🦒

VBAでオブジェクト指向:CallByNameとポリモーフィズムで分岐なしで処理をわける方法

2022/11/15に公開

TwitterのExcel先輩諸氏のツイートを眺めていたら、次のような面白そうなツイートがあった。

一般的にはTypeNameなどで分岐すればよいのですが、完全に遊びでCallByNameとポリモーフィズムで分岐しないでツイートの内容を実現してみました。

クラスの普及のために書いてますが、こんなテクニックはほとんど使わないと思います。
あくまで遊びということで。

クラス定義

処理クラスのインターフェースを定義

まずは処理クラスとなるCommandを定義します。

Commandクラス
Option Explicit

Public Sub Execute()
End Sub

インターフェースですので空実装です。

これをそれぞれのImplmentsした実装クラスを書きます。

二次元配列を処理するクラスを定義

まずは二次元配列用です。

ArrayCommandクラス
Option Explicit
Implements Command

Private mArray As Variant

Public Sub Init(pArray As Variant)
    mArray = pArray
End Sub

Private Sub Command_Execute()
    'mArrayを使って何らかの処理をする
    Debug.Print "Array!"
End Sub

Rangeを処理するクラスを定義

次にRange用です。

RangeCommandクラス
Option Explicit
Implements Command

Private mRange As Range

Public Sub Init(pRange As Range)
    Set mRange = pRange
End Sub

Private Sub Command_Execute()
    'mRangeを使った処理
    Debug.Print "Range!"
End Sub

それぞれのクラスを生成するファクトリークラスを定義

CallByNameで実行されるメソッドを定義します。
ここにそれぞれのクラスを生成して返します。

CommandFactoryクラス
Option Explicit

Public Function CreateCommandForVariant(pArray As Variant) As Command

    Dim vCommand As ArrayCommand
    Set vCommand = New ArrayCommand

    Call vCommand.Init(pArray)
    
    Set CreateCommandForVariant = vCommand

End Function

Public Function CreateCommandForRange(pRange As Range) As Command

    Dim vCommand As RangeCommand
    Set vCommand = New RangeCommand

    Call vCommand.Init(pRange)
    
    Set CreateCommandForRange = vCommand

End Function

ポイントはForXXXのXXXが型名であることです。

標準モジュールの定義

標準モジュールは1つです。テスト用のPrivateプロシージャがありますので、ここから実行できます。なるべくツイートにあったファンクションや実装は残しています。

Mainモジュール
Option Explicit

Function Func(a_Items As Variant)
   
   Dim vCommand As Command
   Set vCommand = ReflectionCommandFactory(a_Items)
   Call vCommand.Execute
   
End Function

Private Function ReflectionCommandFactory(pItems As Variant) As Command

    Dim vCommand As Command
    Set vCommand = CallByName(New CommandFactory, ConvertMethodName(pItems), VbMethod, pItems)

    Set ReflectionCommandFactory = vCommand
End Function

Private Function ConvertMethodName(pItems As Variant) As String
    ConvertMethodName = "CreateCommandFor" & Replace(Replace(TypeName(pItems), "(", ""), ")", "")
End Function

Private Sub TestArray()

    Dim vArray(1 To 2, 1 To 32) As Variant
    Call Func(vArray)

End Sub

Private Sub TestRange()

    Dim vRange As Range
    Set vRange = ActiveWorkbook.ActiveSheet.Range(Cells(1, 1), Cells(10, 1))
    Call Func(vRange)

End Sub

キモとなるのは、ConvertMethodName()ファンクションです。
ここで、CommandFactoryのメソッド名+型名を作っています。

二次元配列の型名を取得するとVariant()とカッコが付いてくるので、これは除去しています。
本当にVariantが来たときはちょっと考えないといけないですが、そこは目をつぶってください。

あとはポリモーフィズムで実装クラスのことは標準モジュールは知ることなく、それぞれの処理を実行できます。

これのよいところ

分岐がないのでテストがしやすいです。
また、クラス単体でテストコードが書けますので、ロジックに集中できます。

コードもシンプルです。クラスに慣れていないとどうやって実行されるのか分からないかもしれません。
気になる方は実際に張り付けて実行してみてください。

Discussion