t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBAでインターフェースを使って引数付きのコンストラクタを実現する。

タイトルにコンストラクタとあるが、正確にはコンストラクタもどきである。

先日VBAのAriawaseライブラリのCore.basに書かれたInitのコードに感銘を受けた話を書いた。

ただ、私の個人的な好みとしては、外部DLLの参照よりはVBAの基本機能だけで解決してしまいたい。
そこで、Initializableインターフェースを作って対処してみた。

こちらはクラスモジュール:Initializableのコード

Public Function Init(p() As Variant) As Object
End Function

次に、Initメソッドを実装するクラスモジュール例:SheetExのコード
SheetExは、Worksheetのラッパーとして作った。
現時点ではMaxRow,MaxColumnしか無いが、いろいろ拡張できるはず。
(アイデアが無いだけ)

Implements Initializable
Dim a_self As Worksheet
Private Ready As Boolean

Enum ErrCode
    'エラーNo.0~512はシステムエラー用に予約されている。
    InitTwice = 513
    NotReady = 514
End Enum

Private Sub ReadyCheck()
    If Not Ready Then Err.Raise ErrCode.NotReady, _
        TypeName(Me), "クラス「" & TypeName(Me) & "」は初期化されていません。"
End Sub

Property Get Self() As Worksheet
    ReadyCheck
    Set Self = a_self
End Property

Private Function Initializable_Init(p() As Variant) As Object
    If Ready Then Err.Raise ErrCode.InitTwice, TypeName(Me), _
        "クラス「" & TypeName(Me) & "」はすでに初期化されています。"
    Set a_self = p(0)
    Set Initializable_Init = Me
    Ready = True
End Function

Property Get MaxRow() As Long
    ReadyCheck
    MaxRow = a_self.UsedRange.Find("*", , xlFormulas, , xlByRows, xlPrevious).Row
End Property

Property Get MaxColumn() As Long
    ReadyCheck
    MaxColumn = a_self.UsedRange.Find("*", , xlFormulas, , xlByColumns, xlPrevious).Column
End Property

このSheetExは、コンストラクタであるInitメソッドでWorksheetオブジェクトを受け取ってa_selfに格納する。MaxRowやMaxColumnを取得するメソッドを追加している。
※シート自体を取り出すのに、Selfメソッドが必要な部分はイケてないがデフォルトメソッドの設定が出来なかったので妥協。

次に、ObjectのInitメソッドを呼び出す汎用Init関数
これは標準モジュールに記載するが、Ariawaseを使用している場合は関数名が被るので要変更。

Function Init(o As Initializable, ParamArray p()) As Object
    'ParamArrayで受け取った配列pは、
    'なぜかそのまま別関数の引数に配列として渡すことが出来ない。
    'そのため、面倒だが一旦別配列p2に格納している。
    Dim p2() As Variant
    ReDim p2(UBound(p))
    For i = 0 To UBound(p)
        If IsObject(p(i)) Then
            Set p2(i) = p(i)
        Else
            Let p2(i) = p(i)
        End If
    Next
    Set Init = o.Init(p2)
End Function

Init関数の引数をInitializable型にしているのが、汎用化の肝である。
これで、Initializableインターフェースを実装しているクラスであればこのInit関数で初期化ができる。
つまり、Ariawaseと同じように、引数付きコンストラクタもどきができる。

Sub test()
    Dim x As SheetEx
    Set x = Init(New SheetEx, Sheet1)
    Debug.Print x.Self.Name
    Debug.Print x.MaxRow, x.MaxColumn
End Sub

難点は、インターフェースのためにひとつクラスモジュールが増えるところ。
このあたりがVBAのイケてない点であるが、元々クラスモジュールを多様するコードというのはAriawase以外にお目にかかったことが無いので邪魔になるほどではないと思う。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。