Callbackメソッドを追加する

Callbackメソッドを追加する

前回

akashi-keirin.hatenablog.com

作成したDekosukeクラス。

そのイベントリスナ用ImmWndCallbackクラスに、新たなメソッドを追加してみる。

目次

こんなことができます

今回は、

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プロパティを「デコスケ」に変更しようと試みているが、実際に実行すると、

f:id:akashi_keirin:20201231131329g:plain

f:id:akashi_keirin:20201231131324j:plain

こうなる。

インターフェース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を受け取るようにした。

二つ目の引数NewNameByRefにしたのは、メソッド側で値を変更できるようにするため。

今回の場合、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が「デコスケ」だったら、イミディエイトにメッセージを表示し、NewNameOldNameの値を代入する。

NewNameByRef指定なので、呼び出し元の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

f:id:akashi_keirin:20201231131329g:plain

こんなふうに次々とメッセージボックスが表示されるが、

(***)のところでNameプロパティを「デコスケ」に変更しようとしたというのに、最後の「それは良いお考えです。」というセリフは「諸葛亮」が言ったことになっている。

f:id:akashi_keirin:20201231131324j:plain

イミディエイトはこの通り。

ちゃんとImmWndCallback_BeforeChangeNameメソッドが仕事をしているということだ。

おわりに

いやはや、面白いものだ。

追記

肝腎なことを書くのを忘れていた。

今回の内容について、

Propertyの設定値に制限加えるぐらい、Property Letプロシージャ内でやりゃいーだろうが。そのためのProperty Letプロシージャじゃねえのかよ!

と思っただろうか。

なるほど。一理ある。

しかし、私はこう思うのだ。

これぞまさに、「データとインターフェースの分離」というやつでねえのかい?

と。

たとえば、今回の場合、〝特定の名前の変更を拒否する〟という動作を、ImmWndCallbackクラスに持たせた。これによって、Dekosukeクラスに直接〝特定の名前の変更を拒否する〟という動作を書く必要がなくなった。

これにより、Nameプロパティ変更時にやらせたい動作を、実に柔軟に、自由自在に書くことができるようになるのである!

もちろん、アホみたいにたくさんクラスモジュールを使うことにはなるけど。

今のところ、このように理解しています。

もし、

おおお……、何という浅はか者よ……。おまえの考えは間違うておる……。

と思う人がいたら、教えろ教えてくだされ。