クラスモジュール
VBA で複雑なマクロを書いていると、「関連する変数とプロシージャをまとめたい」「同じような処理を使いまわしたい」と感じることがあります。こうした課題を解決するのがクラスモジュールです。クラスモジュールを使えば、データ(プロパティ)とそのデータを操作する処理(メソッド)を一つの「部品」としてまとめ、独自のオブジェクトを作成できます。
この記事では、クラスモジュールの基本的な作り方から、実務で活用できる実践的なパターンまで詳しく解説します。
クラスモジュールとは
クラスモジュールは、VBA で独自のオブジェクト(カスタムオブジェクト)を定義するための仕組みです。Excel の Workbook や Worksheet、Range がオブジェクトであるように、自分で設計した「オブジェクトの設計図」を作ることができます。
クラスとオブジェクトの関係
クラスとオブジェクトの関係は、「設計図」と「実体」の関係に例えられます:
- クラス = 設計図(プロパティとメソッドの定義)
- インスタンス(オブジェクト) = 設計図をもとに作った実体
例えば、「従業員」というクラスを設計図として作成し、そこから「田中太郎」「佐藤花子」といった具体的なインスタンスを生成する、というイメージです。
クラスモジュールを使うメリット
| 観点 | 標準モジュールのみ | クラスモジュール活用 |
|---|---|---|
| データと処理の関係 | バラバラに管理 | 一つにまとめて管理 |
| 変数のスコープ | モジュールレベルで管理 | オブジェクト内にカプセル化 |
| コードの再利用 | コピー&ペーストが必要 | モジュールをドラッグ&ドロップ |
| メンテナンス性 | 変更の影響範囲が広い | 影響を局所化できる |
| IntelliSense | 利用しにくい | プロパティ/メソッドが補完される |
一般的なオブジェクト指向プログラミングには「カプセル化」「継承」「多態性(ポリモーフィズム)」の3大要素がありますが、VBA
ではカプセル化のみがサポートされています。継承は直接サポートされていませんが、Implements
キーワードによるインターフェースの実装は可能です。
クラスモジュールの作成と基本
クラスモジュールの挿入方法
- VBE(Visual Basic Editor)を開く(Alt + F11)
- メニューバーから「挿入」→「クラスモジュール」を選択
- プロパティウィンドウの
(Name)でクラス名を設定(例:CEmployee)
クラス名の先頭に C を付ける(例:
CEmployee、CProduct)慣例がよく使われます。これにより標準モジュールやワークシートオブジェクトと区別しやすくなります。ただし、必須ではありません。
最初のクラスを作る
まず、従業員情報を管理するシンプルなクラスを作成します。
' === クラスモジュール: CEmployee ===
' プライベート変数(外部から直接アクセスできない)
Private pId As Long
Private pName As String
Private pDepartment As String
' --- プロパティ ---
' Id プロパティ(読み書き)
Public Property Get Id() As Long
Id = pId
End Property
Public Property Let Id(ByVal value As Long)
pId = value
End Property
' Name プロパティ(読み書き)
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(ByVal value As String)
pName = value
End Property
' Department プロパティ(読み書き)
Public Property Get Department() As String
Department = pDepartment
End Property
Public Property Let Department(ByVal value As String)
pDepartment = value
End Property
' --- メソッド ---
' 従業員情報を文字列で返す
Public Function GetInfo() As String
GetInfo = "ID: " & pId & ", 名前: " & pName & ", 部署: " & pDepartment
End Function
' 従業員情報をイミディエイトウィンドウに表示
Public Sub PrintInfo()
Debug.Print GetInfo()
End Sub
クラスのインスタンス化と使用
標準モジュールからクラスを使用します。
Sub UseEmployeeClass()
' インスタンスの生成
Dim emp As CEmployee
Set emp = New CEmployee
' プロパティの設定
emp.Id = 1001
emp.Name = "田中太郎"
emp.Department = "営業部"
' メソッドの呼び出し
emp.PrintInfo ' => ID: 1001, 名前: 田中太郎, 部署: 営業部
' 複数のインスタンスを作成できる
Dim emp2 As CEmployee
Set emp2 = New CEmployee
emp2.Id = 1002
emp2.Name = "佐藤花子"
emp2.Department = "開発部"
emp2.PrintInfo ' => ID: 1002, 名前: 佐藤花子, 部署: 開発部
' オブジェクトの解放
Set emp = Nothing
Set emp2 = Nothing
End Sub
プロパティの種類
クラスのプロパティは、3 種類の Property プロシージャで定義します。
Property Get - 値の取得
外部からプロパティの値を読み取るときに使用します。
Public Property Get Name() As String
Name = pName
End Property
Property Let - 値の設定(値型)
外部からプロパティに値型(String、Long、Boolean 等)の値を設定するときに使用します。
Public Property Let Name(ByVal value As String)
' バリデーションも追加できる
If Len(value) = 0 Then
Err.Raise 5, , "名前は空にできません。"
End If
pName = value
End Property
Property Set - 値の設定(オブジェクト型)
外部からプロパティにオブジェクト型(Worksheet、Range 等)の値を設定するときに使用します。
Private pSheet As Worksheet
Public Property Get Sheet() As Worksheet
Set Sheet = pSheet
End Property
Public Property Set Sheet(ByVal value As Worksheet)
Set pSheet = value
End Property
読み取り専用プロパティ
Property Get だけを定義し、Property Let / Property Set を省略すると、読み取り専用のプロパティになります。
Private pCreatedAt As Date
' 読み取り専用プロパティ
Public Property Get CreatedAt() As Date
CreatedAt = pCreatedAt
End Property
Property Get
のみを定義したプロパティは外部から値を設定できないため、意図しないデータの変更を防げます。これがカプセル化のメリットの一つです。
初期化と終了処理
Class_Initialize - コンストラクタ
クラスのインスタンスが生成されたとき(New したとき)に自動的に実行されるイベントです。プロパティの初期値設定に使用します。
Private Sub Class_Initialize()
' デフォルト値の設定
pId = 0
pName = "未設定"
pDepartment = "未所属"
pCreatedAt = Now
End Sub
Class_Terminate - デストラクタ
クラスのインスタンスが破棄されたとき(Set obj = Nothing やスコープ外になったとき)に自動的に実行されるイベントです。クリーンアップ処理に使用します。
Private Sub Class_Terminate()
' リソースの解放
Set pSheet = Nothing
Debug.Print "CEmployee インスタンスが破棄されました。"
End Sub
Class_Initialize は他のプログラミング言語の「コンストラクタ」、Class_Terminate
は「デストラクタ」に相当します。ただし、VBA の Class_Initialize
は引数を受け取れません。引数付きの初期化が必要な場合は、別途 Init
メソッドを用意するパターンが一般的です。
Init メソッドパターン(引数付き初期化)
' クラスモジュール: CEmployee
Public Sub Init(ByVal id As Long, ByVal empName As String, ByVal dept As String)
pId = id
pName = empName
pDepartment = dept
End Sub
' 標準モジュールでの使用
Sub UseInitMethod()
Dim emp As CEmployee
Set emp = New CEmployee
emp.Init 1001, "田中太郎", "営業部"
emp.PrintInfo ' => ID: 1001, 名前: 田中太郎, 部署: 営業部
End Sub
カプセル化の実践
カプセル化とは、データ(プロパティ)と処理(メソッド)を一つにまとめ、内部の実装を外部から隠蔽する考え方です。
Public 変数 vs Property プロシージャ
クラスのメンバ変数を Public で宣言するだけでもプロパティとして使えますが、バリデーションや加工処理ができません。
' === 方法1: Public 変数(シンプルだがバリデーション不可)===
Public Name As String
Public Age As Long
' === 方法2: Property プロシージャ(推奨)===
Private pName As String
Private pAge As Long
Public Property Get Age() As Long
Age = pAge
End Property
Public Property Let Age(ByVal value As Long)
' バリデーション
If value < 0 Or value > 150 Then
Err.Raise 5, "CEmployee", "年齢は 0〜150 の範囲で指定してください。"
End If
pAge = value
End Property
Public 変数はシンプルですが、任意の値を自由に代入できてしまいます。重要なデータには Property Let でバリデーションを入れることを推奨します。ただし、シンプルなデータ保持だけが目的なら Public
変数でも十分です。
内部メソッドの隠蔽
クラス内部でのみ使用するヘルパーメソッドは Private で宣言します。
' 外部から呼び出せるメソッド
Public Function GetFormattedInfo() As String
GetFormattedInfo = FormatId() & " - " & pName & "(" & pDepartment & ")"
End Function
' 内部でのみ使用するヘルパーメソッド
Private Function FormatId() As String
FormatId = Format(pId, "EMP-0000")
End Function
Collection と組み合わせたパターン
クラスのインスタンスを Collection に格納して管理するのは、VBA で最もよく使われるパターンの一つです。
Sub ManageEmployees()
' 従業員コレクションの作成
Dim employees As Collection
Set employees = New Collection
' データを読み込んでインスタンスを生成
Dim ws As Worksheet
Set ws = Sheets("従業員データ")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
Dim i As Long
For i = 2 To lastRow
Dim emp As CEmployee
Set emp = New CEmployee
emp.Init ws.Cells(i, 1).Value, ws.Cells(i, 2).Value, ws.Cells(i, 3).Value
' キーとして ID を使って追加
employees.Add emp, CStr(emp.Id)
Next i
' コレクション内のオブジェクトを使用
Dim e As CEmployee
For Each e In employees
Debug.Print e.GetInfo()
Next e
' キーでオブジェクトを取得
Dim target As CEmployee
Set target = employees("1001")
Debug.Print "検索結果: " & target.GetInfo()
Set employees = Nothing
End Sub
実践的な活用例
活用例1:処理時間計測クラス
マクロの処理時間を簡単に計測するためのユーティリティクラスです。
' === クラスモジュール: CStopwatch ===
Private pStartTime As Double
Private pLabel As String
Private Sub Class_Initialize()
pLabel = "処理"
End Sub
' 計測ラベルの設定
Public Property Let Label(ByVal value As String)
pLabel = value
End Property
' 計測開始
Public Sub Start()
pStartTime = Timer
End Sub
' 経過時間(秒)を取得
Public Function GetElapsed() As Double
GetElapsed = Timer - pStartTime
End Function
' 経過時間をイミディエイトウィンドウに出力
Public Sub PrintElapsed()
Debug.Print pLabel & ": " & Format(GetElapsed(), "0.000") & " 秒"
End Sub
' 経過時間をステータスバーに表示
Public Sub ShowInStatusBar()
Application.StatusBar = pLabel & ": " & Format(GetElapsed(), "0.000") & " 秒"
End Sub
Sub MeasurePerformance()
Dim sw As CStopwatch
Set sw = New CStopwatch
sw.Label = "データ処理"
sw.Start
' 処理を実行
Dim i As Long
For i = 1 To 100000
' 何らかの処理
Next i
sw.PrintElapsed ' => データ処理: 0.125 秒
Set sw = Nothing
End Sub
活用例2:ログ出力クラス
処理結果やエラー情報をログシートに記録するクラスです。
' === クラスモジュール: CLogger ===
Private pLogSheet As Worksheet
Private Sub Class_Initialize()
' ログシートの取得(なければ作成)
On Error Resume Next
Set pLogSheet = ThisWorkbook.Sheets("Log")
On Error GoTo 0
If pLogSheet Is Nothing Then
Set pLogSheet = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
pLogSheet.Name = "Log"
pLogSheet.Range("A1").Value = "日時"
pLogSheet.Range("B1").Value = "レベル"
pLogSheet.Range("C1").Value = "メッセージ"
pLogSheet.Range("A1:C1").Font.Bold = True
End If
End Sub
' ログの書き込み(共通処理)
Private Sub WriteLog(ByVal level As String, ByVal message As String)
Dim nextRow As Long
nextRow = pLogSheet.Cells(pLogSheet.Rows.Count, 1).End(xlUp).Row + 1
pLogSheet.Cells(nextRow, 1).Value = Now
pLogSheet.Cells(nextRow, 2).Value = level
pLogSheet.Cells(nextRow, 3).Value = message
' レベルに応じた色分け
Select Case level
Case "ERROR"
pLogSheet.Cells(nextRow, 2).Interior.Color = RGB(255, 200, 200)
Case "WARN"
pLogSheet.Cells(nextRow, 2).Interior.Color = RGB(255, 255, 200)
Case "INFO"
pLogSheet.Cells(nextRow, 2).Interior.Color = RGB(200, 230, 255)
End Select
End Sub
' 情報ログ
Public Sub Info(ByVal message As String)
WriteLog "INFO", message
End Sub
' 警告ログ
Public Sub Warn(ByVal message As String)
WriteLog "WARN", message
End Sub
' エラーログ
Public Sub LogError(ByVal message As String)
WriteLog "ERROR", message
End Sub
Sub ProcessWithLogging()
Dim logger As CLogger
Set logger = New CLogger
logger.Info "処理を開始します。"
On Error GoTo ERROR_HANDLER
' データの処理
Dim ws As Worksheet
Set ws = Sheets("データ")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
logger.Info lastRow - 1 & " 件のデータを処理します。"
Dim i As Long
Dim errorCount As Long
For i = 2 To lastRow
If IsEmpty(ws.Cells(i, 1).Value) Then
logger.Warn "行 " & i & ": データが空です。スキップしました。"
errorCount = errorCount + 1
End If
Next i
logger.Info "処理が完了しました。エラー: " & errorCount & " 件"
Set logger = Nothing
Exit Sub
ERROR_HANDLER:
logger.LogError "エラーが発生しました: " & Err.Description
Set logger = Nothing
End Sub
活用例3:設定管理クラス
マクロの設定値を一元管理するクラスです。設定シートから値を読み込み、プロパティとして提供します。
' === クラスモジュール: CConfig ===
Private pSettings As Object ' Dictionary
Private Sub Class_Initialize()
Set pSettings = CreateObject("Scripting.Dictionary")
' 設定シートから値を読み込み
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Sheets("設定")
On Error GoTo 0
If ws Is Nothing Then Exit Sub
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
Dim i As Long
For i = 2 To lastRow
Dim key As String
key = CStr(ws.Cells(i, 1).Value)
If key <> "" Then
pSettings(key) = ws.Cells(i, 2).Value
End If
Next i
End Sub
' 設定値の取得
Public Function GetValue(ByVal key As String, Optional ByVal defaultValue As Variant = "") As Variant
If pSettings.Exists(key) Then
GetValue = pSettings(key)
Else
GetValue = defaultValue
End If
End Function
' 設定値の存在確認
Public Function HasKey(ByVal key As String) As Boolean
HasKey = pSettings.Exists(key)
End Function
Private Sub Class_Terminate()
Set pSettings = Nothing
End Sub
Sub UseConfig()
Dim config As CConfig
Set config = New CConfig
' 設定シートの値を取得
Dim outputPath As String
outputPath = config.GetValue("出力先フォルダ", "C:\output\")
Dim maxRows As Long
maxRows = CLng(config.GetValue("最大行数", 1000))
Debug.Print "出力先: " & outputPath
Debug.Print "最大行数: " & maxRows
Set config = Nothing
End Sub
ユーザー定義型(Type)との比較
VBA にはクラス以外にも、関連するデータをまとめる**ユーザー定義型(Type)**があります。
' === ユーザー定義型(Type)===
Type Employee
Id As Long
Name As String
Department As String
End Type
' === クラス ===
' CEmployee クラスモジュール(前述)
| 項目 | ユーザー定義型(Type) | クラスモジュール |
|---|---|---|
| メソッドの定義 | × 不可 | ○ 可能 |
| バリデーション | × 不可 | ○ Property で実装可能 |
| カプセル化 | × 不可 | ○ Private / Public で制御 |
| IntelliSense | ○ メンバが補完される | ○ プロパティ/メソッドが補完される |
| 初期化/終了処理 | × 不可 | ○ Initialize / Terminate |
| Collection に格納 | × 不可 | ○ 可能 |
| パフォーマンス | ○ 高速 | △ やや遅い |
| 複雑さ | ○ シンプル | △ やや複雑 |
データの保持だけが目的なら Type
で十分です。メソッドやバリデーションが必要な場合はクラスモジュールを使いましょう。
注意点とベストプラクティス
1. オブジェクトの解放
クラスのインスタンスを使い終わったら、Set obj = Nothing で明示的に解放しましょう。特にループ内でインスタンスを生成する場合は重要です。
2. クラス名は明確に
クラス名は何を表すオブジェクトかが一目でわかる名前にしましょう。C プレフィックスを付ける慣例が広く使われています(CEmployee、CLogger、CConfig)。
3. Public 変数は必要に応じて使う
すべてのプロパティに Property Get / Property Let を書く必要はありません。シンプルなデータ保持だけなら Public 変数でも構いません。バリデーションや読み取り専用が必要な場合に Property プロシージャを使い分けましょう。
4. クラスの粒度を適切に保つ
一つのクラスに詰め込みすぎず、責務を明確にしましょう。「ログを管理するクラス」「設定を管理するクラス」「従業員データを表すクラス」のように、一つのクラスが一つの役割を持つのが理想です。
5. モジュールの再利用
クラスモジュールは VBE のプロジェクトエクスプローラーからドラッグ&ドロップで別のプロジェクトにコピーできます。汎用的なクラスを一度作成すれば、複数のマクロで再利用できるのが大きなメリットです。
練習問題
まとめ
- クラスモジュールを使うと、データ(プロパティ)と処理(メソッド)をまとめた独自のオブジェクトを作成できる
- クラスは「設計図」であり、
Newキーワードで「インスタンス(実体)」を生成して使用する - Property Get / Let / Set プロシージャでプロパティを定義し、バリデーションや読み取り専用の制御が可能になる
- Class_Initialize と Class_Terminate でインスタンスの初期化とクリーンアップを行う
- VBA のオブジェクト指向ではカプセル化が最も重要な概念で、
Private変数とPropertyプロシージャで実現する - シンプルなデータ保持だけなら
Public変数やユーザー定義型(Type)で十分、メソッドやバリデーションが必要な場合にクラスを使う - 汎用的なクラスは別のプロジェクトにドラッグ&ドロップでコピーでき、再利用性が高い