Callbackメソッドを追加する
前回
作成したDekosuke
クラス。
そのイベントリスナ用ImmWndCallback
クラスに、新たなメソッドを追加してみる。
目次
- こんなことができます
- インターフェースIDekosukeCallbackの変更
- ImmWndCallbackクラスへのメソッド追加
- DekosukeクラスのNameプロパティ変更
- 使ってみる
- おわりに
- 追記
こんなことができます
今回は、
DekosukeクラスのNameプロパティを変更しようとしたとき、新しい名前が「デコスケ」だったら、変更を拒否して元の名前を維持し、イミディエイトにもその旨出力する
というものにしたい。
Private Sub testDekosuke01() Dim ds As Dekosuke Set ds = New Dekosuke Set ds.Callback = New ImmWndCallback Call ds.Say("ち~んw") ds.Name = "諸葛亮" Call ds.Say("他にすることはないのですか?") ds.Name = "デコスケ" '……(*)' Call ds.Say("それは良いお考えです。") End Sub
上掲コードでは、(*)
のところで、Dekosuke
オブジェクトのName
プロパティを「デコスケ
」に変更しようと試みているが、実際に実行すると、
こうなる。
インターフェースIDekosukeCallbackの変更
まずは、メソッドの追加。
今回作成するメソッドは、名前を変更する前に発生するイベントのようなものなので、名前は「BeforeChangeName
」にする。
リスト1 クラスモジュールIDekosukeCallback
Option Explicit Public Sub AfterSaid(ByVal Message As String, _ Optional ByVal Speaker As String) End Sub Public Sub BeforeChangeName( _ ByVal OldName As String, _ ByRef NewName As String) End Sub
二つ目が追加したもの。
変更前の名前OldName
と変更後の名前NewName
を受け取るようにした。
二つ目の引数NewName
をByRef
にしたのは、メソッド側で値を変更できるようにするため。
今回の場合、NewName
の値が「デコスケ
」だったら、
NewName = OldName
のように、元の名前をセットすることを想定している。
ImmWndCallbackクラスへのメソッド追加
インターフェースに定義したメソッドが増えたので、ImmWndCallback
クラスにもメソッドを追加せねばならない。
追加したメソッドの部分のコードは、次のリスト2の通り。
リスト2 クラスモジュールImmWndCallback
Private Sub IDekosukeCallback_BeforeChangeName( _ ByVal OldName As String, _ ByRef NewName As String) If NewName = "デコスケ" Then Debug.Print "名前「デコスケ」は使用できません。" & vbCrLf & _ "元の名前「" & OldName & "」に戻します。" NewName = OldName End If End Function
見ての通り、NewName
が「デコスケ
」だったら、イミディエイトにメッセージを表示し、NewName
にOldName
の値を代入する。
NewName
はByRef
指定なので、呼び出し元のNewName
にも変更が反映される。
DekosukeクラスのNameプロパティ変更
あとは、Dekosuke
クラスのName
プロパティの内部を変更しないといけない。
現段階のDekosuke
クラスモジュールのコードは、次に示すリスト2の通り。
リスト2 クラスモジュールDekosuke
Option Explicit Private m_Name As String Public Callback As IDekosukeCallback Public Property Let Name(ByVal ArgString As String) m_Name = ArgString '……(**)' End Property Public Property Get Name() As String Name = m_Name End Property Public Sub Say(ByVal Message As String) Dim tmp As String tmp = m_Name & "曰く、「" & Message & "」" Call MsgBox(tmp) If Not Me.Callback Is Nothing Then Call Me.Callback.AfterSaid(Message, m_Name) End If End Sub Private Sub Class_Initialize() m_Name = "名無しさん" End Sub
(**)
のところからも分かる通り、現状では、受けとった文字列をそのままm_Name
に代入しているだけ。
これを、次のように変更する。
Public Property Let Name(ByVal ArgString As String) If Not Me.Callback Is Nothing Then Call Me.Callback.BeforeChangeName(m_Name, ArgString) End If m_Name = ArgString End Property
Callback
プロパティがNothing
でなかったら、Callback
プロパティに代入されているオブジェクトのBeforeChangeName
メソッドを実行する。(本当にひつこいが、Callback
プロパティはIDekosueCallback
型なので、必ずBeforeChangeName
メソッドを持っているのである。)
今回の場合、ImmWndCallback
クラスのBeforeChangeName
を実行することになる。
BeforeChangeName
メソッドの第2引数(この場合はArgString
がそのまま渡される。)が「デコスケ
」でなければ、ImmWndCallback_BeforeChangeName
メソッドの仕様上、ArgString
の値は変更されず、それがm_Name
に代入される。すなわち、Dekosuke
オブジェクトのName
プロパティが変更される。
逆に、BeforeChangeName
メソッドの第2引数が「デコスケ
」だったら、ImmWndCallback_BeforeChangeName
メソッドの仕様上、ArgString
の値がm_Name
の値(つまり、変更前の名前)に変更され、それがm_Name
に代入されることになる。すなわち、Dekosuke
オブジェクトのName
プロパティは(見かけ上)変更されないのである。
これで準備完了。
まさに、「時は来た!」状態である!
使ってみる
次のリスト3を実行する。
リスト3 標準モジュール
Private Sub testDekosuke01() Dim ds As Dekosuke Set ds = New Dekosuke Set ds.Callback = New ImmWndCallback Call ds.Say("ち~んw") ds.Name = "諸葛亮" Call ds.Say("他にすることはないのですか?") ds.Name = "デコスケ" '……(***)' Call ds.Say("それは良いお考えです。") End Sub
こんなふうに次々とメッセージボックスが表示されるが、
(***)
のところでName
プロパティを「デコスケ
」に変更しようとしたというのに、最後の「それは良いお考えです。
」というセリフは「諸葛亮
」が言ったことになっている。
イミディエイトはこの通り。
ちゃんとImmWndCallback_BeforeChangeName
メソッドが仕事をしているということだ。
おわりに
いやはや、面白いものだ。
追記
肝腎なことを書くのを忘れていた。
今回の内容について、
Propertyの設定値に制限加えるぐらい、Property Letプロシージャ内でやりゃいーだろうが。そのためのProperty Letプロシージャじゃねえのかよ!
と思っただろうか。
なるほど。一理ある。
しかし、私はこう思うのだ。
これぞまさに、「データとインターフェースの分離」というやつでねえのかい?
と。
たとえば、今回の場合、〝特定の名前の変更を拒否する〟という動作を、ImmWndCallback
クラスに持たせた。これによって、Dekosuke
クラスに直接〝特定の名前の変更を拒否する〟という動作を書く必要がなくなった。
これにより、Name
プロパティ変更時にやらせたい動作を、実に柔軟に、自由自在に書くことができるようになるのである!
もちろん、アホみたいにたくさんクラスモジュールを使うことにはなるけど。
今のところ、このように理解しています。
もし、
おおお……、何という浅はか者よ……。おまえの考えは間違うておる……。
と思う人がいたら、教えろ教えてくだされ。