Excel VBAをクラスを使って改善しよう: オブザーバーパターンで無限の拡張性を手に入れよう
オブザーバーパターン(Observer Pattern)とは
Observerパターンはご存じでしょうか。
VBAerにはほとんどなじみのない用語かと思います。
Wikipediaを読んでもちょっと分からない感じですが、とても簡単に説明すると次のような関係です。
- 発行者が何かを発行する(例えば新聞)
- 購読者が発行されたものを受け取る(新聞がポストに入る)
- 購読者は新聞をどうにかする。
- 購読者は複数いてよい
- 購読者は受け取ったものをどう使ってもよい
- 一面だけ読む
- テレビ欄だけ読む人
- たまって捨ててしまう
- etc...
ポイントは発行者と購読者が独立していて、購読者の数や種類が変わっても発行者には影響を与えない、という点です。
今回はこれをVBAのイベントを使って実装してみます。
イベントを使わなくてもできるんじゃないの?
できます。Wikipediaにあるようなクラス構成で実装すればできます。
ただ、VBAにはせっかくイベントという言語機能があり、かつ、誰にもまともに使われている感じがしないため、便利なことにも使えるよ、という企画です。
サンプル提示
取り上げるサンプルは、発行者がメッセージをイベントで通知し、購読者はそのイベントを受け取って、購読者それぞれでイベントを処理します。
今回はイミディエイトウィンドウにログ出力するクラスとメッセージボックスを出力するクラスとしました。
イベントの詳細な説明は、ネット上の先輩諸氏が分かりやすく書いてくれていますので、ここではあまりしません。具体的な使い方中心です。
イベントクラス
まずイベントを作成します。今回はメッセージを届けるイベントとしました。
とても単純です。
Option Explicit
Public Event Notify(pMessage As String)
Public Sub Execute(pMessage As String)
RaiseEvent Notify(pMessage)
End Sub
Executeメソッドを実行すると、受け取ったメッセージをイベントで通知します。
発行者
発行者はイベントのオブジェクトを持っています。
イベントを実行して、購読者へ通知をしてもらいます。
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
購読者
購読者もイベントを持っています。
このイベントは発行者が持っているイベントと同じインスタンスです。
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パターンはポリモーフィズムで実装を増やすことができるパターンです。
まず、インターフェースを作成します。
Option Explicit
Public Sub Execute(pMessage As String)
End Sub
これを実装した各ロジッククラスを作成していきます。
イミディエイトウィンドウに出力するロジック
上記のインターフェースを実装します。
Option Explicit
Implements SubscribeStrategy
Private Sub SubscribeStrategy_Execute(pMessage As String)
Debug.Print Now(), pMessage
End Sub
メッセージボックスに出力するロジック
同様に、上記のインターフェースを実装します。
Option Explicit
Implements SubscribeStrategy
Private Sub SubscribeStrategy_Execute(pMessage As String)
MsgBox pMessage
End Sub
メイン
パーツが出揃ったところで、組み合わせて実行してみます。
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