なんでVBAでクラスモジュールを使うのか

f:id:akashi_keirin:20211228103040j:plain

なんでVBAでクラスモジュールを使うのか

基本的には標準モジュールで十分

プログラムを書くときに、モノとして扱った方が楽な場合がある。

モノの機能だけが欲しいのなら、標準モジュールでいい。

機能のまとまりを表す名前を付けて、その中にメソッドをまとめておくのである。

たとえば、ユーザに選ばせたフォルダのフォルダパスを取得するという処理がある。

FileDialogオブジェクトを使う処理だが、毎回いちいちFileDialogオブジェクトを取得してフォルダパスを返す処理を書くのはめんどくさい。 だから、FileDialogオブジェクトをラップしたメソッドを書く。

Option Explicit

Private m_FSO As Object

Public Function GetSelectedFolderPath( _
                  Optional ByVal a_DefaultDir As String, _
                  Optional ByVal a_Title As String) As String
  If m_FSO Is Nothing Then Set m_FSO = CreateObject("Scripting.FileSystemObject")
  Dim ret As String
  ret = ""
  '第1引数省略なら最初に表示するディレクトリをこのブックのある'
  'ディレクトリにする                                          '
  If a_DefaultDir = "" Then _
    a_DefaultDir = ThisWorkbook.Path
  If Not m_FSO.FolderExists(a_DefaultDir) Then _
    a_DefaultDir = ThisWorkbook.Path
  Dim folderPath As String
  Dim isSelected As Boolean
  Dim folderPicker As FileDialog
  Set folderPicker = Application.FileDialog(msoFileDialogFolderPicker)
  With folderPicker
    .InitialFileName = a_DefaultDir
    If a_Title <> "" Then
      .Title = a_Title
    Else
      .Title = "フォルダ選択"
    End If
    isSelected = .Show
    If isSelected Then
      ret = .SelectedItems(1)
    Else
      ret = ""
    End If
  End With
  GetSelectedFolderPath = ret
End Function

このように、たとえばFileDialogUtilという標準モジュールに、GetSelectedFolderPathというメソッドを作っておくと、あとは、

Dim rootDir As String
rootDir = FileDialogUtil.GetSelectedFolderPath

というコードを書くだけで、〝ユーザが選択したフォルダのフルパスを取得する〟という処理が書けるようになる。

この調子で、FileDialogオブジェクトを利用する処理を、標準モジュールFileDialogUtilにまとめておくと、以後プログラムを書くのが非常に楽になる。

このように、FileDialogというモノが使いたい場合でも、機能が欲しいだけなら標準モジュールで十分である。

〝運用でカバー〟的にはなるが、〝メソッド呼び出し時には、必ずモジュール名を記述する〟というルールで用いれば、〝静的クラス〟のような使い方ができる。

クラスモジュールを使いたくなるとき

では、どういうときにクラスモジュールを使いたくなるか。

上記FileDialogUtilでは、モノ自体、つまり利用しようとするFileDialogオブジェクト自体には〝個性〟はなくてもよかった。

利用するFileDialogは、いついかなるときでも〝タダのFileDialogオブジェクト〟なのであって、色も味も身長も体重も、一切の特徴がない〝タダのFileDialogオブジェクト〟である。

それに対して次のような場合はどうか。

〝ある〟テキストファイルをプログラムの中でモノのように扱いたい。

このような場合である。

「〝ある〟テキストファイル」なので、そのモノには個性がある。

〝その〟テキストファイルのファイルパスであったり、〝その〟テキストファイルの内容(要するにテキストデータ)であったり、トータルの行数であったり。

だいたい、プログラムの中でテキストファイルを扱いたいという場合、〝テキストファイルの機能〟を使いたい、という場合はないと思う。(そんな状況は、想像できない。)

〝ある〟具体的なテキストファイルの各行のデータを利用したい。

このような場面のはずである。

そうすると、たとえば、

行数を指定したら、その行のテキストを返してくれたり、全行数を尋ねたら行数を答えてくれたり、指定した行にテキストを挿入したり、……というふうに振る舞ってくれるオブジェクト

があったら、非常に便利なはずである。

しかしながら、デフォルトではそのように振る舞ってくれる便利なオブジェクトは存在しない。

このようなときにクラスモジュールを使いたくなる。

上記の例でいえば、

ファイルパスや指定した行のテキストデータの内容なんかを問い合わせたら、それに応じた値を返してくれて、データを書き加えたり、書き換えたり、削除したり、上書き保存したりすることを命令したら、そのように実行してくれるオブジェクト

を自作するのである。

標準モジュールにメソッドを書いて上記の処理を実現しようと思ったら、必要になるその都度、そのメソッドに当該テキストファイルのフルパスを渡す必要がある。(テキストファイルをテキストファイルというモノとして変数に入れて保持する方法がないのだから、当たり前である。)テキストファイルを開き、読み込んだり書き込んだりする処理自体はまとめておくことができるものの、この〝テキストファイルのフルパスを渡す〟という手順自体は(基本的に)飛ばすことができない。

たとえば、テキストファイルのフルパスと行番号を渡して、当該テキストファイルの当該行のテキストを取得するGetTextData(FilePath As String, LineNumber As Long)というメソッドがあったとする。

そうして、たとえば変数tmpに、そのテキストファイルの3行目と5行目のテキストを結合して代入したい場合、次のようなコードを書くことになる。

Dim tmp As String
tmp = GetTextData("X:\hoge\hoge.txt", 3) & GetTextData("X:\hoge\hoge.txt", 5)

これは非常にめんどくさい上、直感的でない。〝テキストファイルそのもの〟を指し示すオブジェクトが(基本的には)ないので、毎度毎度当該テキストファイルのフルパスを指定することになる。(もちろん、Scripting.TextStreamオブジェクトを使うとか、一旦読み込んだテキストファイルの内容を配列に入れてしまうとか、方法はある。)

その点、たとえば、次のような機能を持ったEasyTextFileというクラスがあったとする。

  • Pathプロパティは、そのテキストファイルのフルパスを表す。
  • Item(LineNumber)メソッドは、指定した行(引数LineNumber)にあるテキストデータを返す。

そうすると、上記「変数tmpに、そのテキストファイルの3行目と5行目のテキストを結合して代入」するという処理は、次のようなコードで書ける。

Dim etf As New EasyTextFile
etf.Path = "X:\hoge\hoge.txt"
Dim tmp = String
tmp = etf.Item(3) & etf.Item(5)

処理の例が単純なので、有難味を感じにくいかも知れないが、ここで用いた「hoge.txt」のデータをプログラム内で参照する回数が増えるほど、恩恵を感じやすくなるはずである。 特に、一つのプログラム内で複数種類のテキストファイルを取り扱わなければならない場合に、より一層便利に感じるはずである。 たとえば、メールを自動で作成するプログラムで、

  • 宛名人に関するデータをRecipient.txtから
  • 差出人に関するデータをSender.txtから
  • 本文に関するデータをMailBody.txtから

それぞれ取り出して使うとする。

上記クラスモジュールEasyTextFileを用いるなら、たとえば、それぞれ

  • recipientData
  • senderData
  • bodyData

のように、役割明示的な変数名を付けてインスタンス化すれば、かなりコードの可読性が上がるはずである。

結論

〝個性のあるモノ〟をプログラム内で扱いたいときに、クラスモジュールを使いたくなる。

おわりに

素人の感想です。