🦒

Excel VBAをクラスを使って改善しよう: オブザーバーパターンで無限の拡張性を手に入れよう

2023/02/16に公開

オブザーバーパターン(Observer Pattern)とは

Observerパターンはご存じでしょうか。
VBAerにはほとんどなじみのない用語かと思います。

https://ja.wikipedia.org/wiki/Observer_パターン

Wikipediaを読んでもちょっと分からない感じですが、とても簡単に説明すると次のような関係です。

  • 発行者が何かを発行する(例えば新聞)
  • 購読者が発行されたものを受け取る(新聞がポストに入る)
  • 購読者は新聞をどうにかする。
  • 購読者は複数いてよい
  • 購読者は受け取ったものをどう使ってもよい
    • 一面だけ読む
    • テレビ欄だけ読む人
    • たまって捨ててしまう
    • etc...

ポイントは発行者と購読者が独立していて、購読者の数や種類が変わっても発行者には影響を与えない、という点です。

今回はこれをVBAのイベントを使って実装してみます。

イベントを使わなくてもできるんじゃないの?

できます。Wikipediaにあるようなクラス構成で実装すればできます。
ただ、VBAにはせっかくイベントという言語機能があり、かつ、誰にもまともに使われている感じがしないため、便利なことにも使えるよ、という企画です。

サンプル提示

取り上げるサンプルは、発行者がメッセージをイベントで通知し、購読者はそのイベントを受け取って、購読者それぞれでイベントを処理します。
今回はイミディエイトウィンドウにログ出力するクラスとメッセージボックスを出力するクラスとしました。

イベントの詳細な説明は、ネット上の先輩諸氏が分かりやすく書いてくれていますので、ここではあまりしません。具体的な使い方中心です。

イベントクラス

まずイベントを作成します。今回はメッセージを届けるイベントとしました。
とても単純です。

NotifyEventクラス
Option Explicit

Public Event Notify(pMessage As String)

Public Sub Execute(pMessage As String)
        RaiseEvent Notify(pMessage)
End Sub

Executeメソッドを実行すると、受け取ったメッセージをイベントで通知します。

発行者

発行者はイベントのオブジェクトを持っています。
イベントを実行して、購読者へ通知をしてもらいます。

Publisherクラス
Option Explicit

Private mEvent As NotifyEvent

Public Sub Init(pEvent As NotifyEvent)
    Set mEvent = pEvent
End Sub

Public Sub SomeProcess(pMessage As String)
    Debug.Print "何かの処理をしました"
    Debug.Print "イベントで処理結果を通知します。発行者は通知するだけで結果がどうやって扱われるかは知りません"
    Call mEvent.Execute(pMessage)
End Sub

購読者

購読者もイベントを持っています。
このイベントは発行者が持っているイベントと同じインスタンスです。

Subscriberクラス
Option Explicit

Private WithEvents mEvent As NotifyEvent
Private mStrategy As SubscribeStrategy

Public Sub Init(pEvent As NotifyEvent, pStrategy As SubscribeStrategy)
    Set mEvent = pEvent
    Set mStrategy = pStrategy
End Sub

Private Sub mEvent_Notify(pMessage As String)
    Call mStrategy.Execute(pMessage)
End Sub

イベントを受け取る購読者は複数作成することができません。
上記の

Private WithEvents mEvent As NotifyEvent

このコードは他のクラスには書くことができません。

そこで購読者が様々なロジックで動作できるようにするため、ロジックを外部から渡しています。Strategyパターンですね。
これで、イベント処理するクラスは1種類でも、振る舞いはいくつでも持てるようになります。

Private mStrategy As SubscribeStrategy

購読者ロジックのインターフェース

Strategyパターンはポリモーフィズムで実装を増やすことができるパターンです。
まず、インターフェースを作成します。

SubscribeStrategyインターフェース
Option Explicit

Public Sub Execute(pMessage As String)
End Sub

これを実装した各ロジッククラスを作成していきます。

イミディエイトウィンドウに出力するロジック

上記のインターフェースを実装します。

SubscribeStrategyDebugPrintクラス
Option Explicit
Implements SubscribeStrategy

Private Sub SubscribeStrategy_Execute(pMessage As String)
    Debug.Print Now(), pMessage
End Sub

メッセージボックスに出力するロジック

同様に、上記のインターフェースを実装します。

SubscribeStrategyMsgBoxクラス
Option Explicit
Implements SubscribeStrategy

Private Sub SubscribeStrategy_Execute(pMessage As String)
    MsgBox pMessage
End Sub

メイン

パーツが出揃ったところで、組み合わせて実行してみます。

Mainモジュール
Option Explicit

Public Sub TestMain()

    'イベントの作成
    Dim vEvent As NotifyEvent
    Set vEvent = New NotifyEvent

    '購読者1の作成(デバッグプリント用)
    Dim vSubscriber1 As Subscriber
    Set vSubscriber1 = New Subscriber

    Dim vDebugPrint As SubscribeStrategyDebugPrint
    Set vDebugPrint = New SubscribeStrategyDebugPrint
    
    Call vSubscriber1.Init(vEvent, vDebugPrint)
    
    '購読者2の作成(メッセージボックス用)
    Dim vSubscriber2 As Subscriber
    Set vSubscriber2 = New Subscriber

    Dim vMsgBox As SubscribeStrategyMsgBox
    Set vMsgBox = New SubscribeStrategyMsgBox
    
    Call vSubscriber2.Init(vEvent, vMsgBox)
    
    '発行者の作成
    Dim vPublisher As Publisher
    Set vPublisher = New Publisher
    'イベントを登録(購読者と同じイベントオブジェクトを参照しているので、このイベントオブジェクトがイベントを発行すると購読者に通知される)
    Call vPublisher.Init(vEvent)
    Call vPublisher.SomeProcess("処理成功")

End Sub

発行者と購読者にそれぞれ同じイベントを渡しておき、イベントを実行すると購読者はイベントに応じてメソッドをそれぞれ実行します。

実行すると、

このようなメッセージボックスが出力されます。

同様に、イミディエイトウィンドウにもメッセージが出力されます。

2023/02/16 21:59:00 処理成功

購読者にメール通知を含めるにはどうすればよいか

ここでもらったメッセージを出力するだけでなく、メールで通知するような仕組みを導入することとします。

この場合、購読者としてメール通知用のクラスを上記のように作成し、メイン処理でイベントと紐づけてあげることで、発行者は何も変更なくメール通知ができるようになります。

ほかの使い道

さらに発行者へ何らかを返す仕組み(コールバック)もできます。
別のロジックへパイプライン処理のようなこともできます。

いわゆる、Pub/Subモデルのようなことができます。
ただし非同期ではないので、非同期にするにはもう少し工夫が必要です。

最後に

ちょっと今回も難しかったですが、クラスを使い倒したいというあなたのために、クラスを理解できるようにするための入門書を作成中です。
それではまた。

Discussion