自身のインスタンスを返すクラス

自分自身のインスタンスを返すクラス

Attribute VB_PredeclaredId = False のとき

クラスモジュールをデフォルトで使うときは、

Attribute VB_PredeclaredId = False

である。

このとき、クラスのメソッドやプロパティは、インスタンス化した後でないと利用できない。

たとえば、次のようなクラスがあったとする。

クラスモジュール HelloWorld
Option Explicit

'Constants'
Private Const DEFAULT_MESSAGE As String = "Hello, World!"

'Module Level Variables'
Private message_ As String

'Properties'
Public Property Get Message() As String
  If message_ = "" Then _
    Message = DEFAULT_MESSAGE: Exit Property
  Message = message_
End Property
Public Property Let Message(ByVal value_ As String)
  If Len(value_) > 20 Then message_ = "": Exit Property
  message_ = value_
End Property

'Constructor'
Private Sub Class_Initialize()
  message_ = DEFAULT_MESSAGE
End Sub

'Methods'
Public Sub sayHello()
  Call MsgBox(message_)
End Sub

無駄に長くて済まない。

Messageというプロパティと、sayHelloというメソッドを持ったクラス。

MessageプロパティはRead/Writeで、デフォルトでは「Hello, World!」という値になるようにしている。あと、せっかくProperty Letを使うので、無駄に20字を超えると空文字にするようにしている。

使ってみる

このHelloWorldクラスを利用してみる。

リスト1 標準モジュール
Public Sub disposable01()
  HelloWorld.sayHello
End Sub

たったこれだけ。インスタンス化せずに使おうとしてみる。既にコード入力時から入力補完も利かないのでダメだろうと予想がつく。実行してみると、

f:id:akashi_keirin:20190429112625j:plain

予想通り、そもそもコンパイルが通らず、実行不可。

Attribute VB_PredeclaredId = True のとき

そこで、一旦このHelloWorld.clsをエクスポートして、エディタで開く。

f:id:akashi_keirin:20190429112628j:plain

このように、Attribute VB_PredeclaredIdのところの右辺をTrueに変えて保存する。

HelloWorld.clsをインポートして、再度リスト1を実行。

f:id:akashi_keirin:20190429112630j:plain

今度は無事実行できた。

ちなみに、次のようにしても同じ結果が出る。

スト2 標準モジュール
Public Sub disposable01()
  Dim greeterMan1 As New HelloWorld
  greeterMan1.sayHello
End Sub

つまり、Attribute VB_PredeclaredId = Trueのときは、インスタンス化してもしなくてもクラスのメソッド、プロパティが利用可能だということらしい。

自身のインスタンスを返すメソッド

ならば、自身のインスタンスを返すメソッドを内包させることができるはず。

上のHelloWorldクラスのコードを次のように書き換える。

クラスモジュール HelloWorld
Option Explicit

'///Attribute VB_PredeclaredId = True///'

'Constants'
Private Const DEFAULT_MESSAGE As String = "Hello, World!"

'Module Level Variables'
Private message_ As String

'Properties'
Public Property Get Message() As String
  If message_ = "" Then _
    Message = DEFAULT_MESSAGE: Exit Property
  Message = message_
End Property
Public Property Let Message(ByVal value_ As String)
  If Len(value_) > 20 Then message_ = "": Exit Property
  message_ = value_
End Property

'Constructor'
Private Sub Class_Initialize()
  message_ = DEFAULT_MESSAGE
End Sub

'Methods'
Public Sub sayHello()
  Call MsgBox(message_)
End Sub

Public Function getInstance( _
         ByVal messageString As String) As HelloWorld  '……(*)'
  Dim ret As HelloWorld
  Set ret = New HelloWorld
  ret.Message = messageString  '……(**)'
  Set getInstance = ret
End Function

冒頭に入れたように、Attribute VB_PredeclaredIdTrueにした場合は、コメントで明示しておいた方がいいと思う。

新たに加えたのは(*)のgetInstanceメソッド。

ちょっと気をつけないといけないのは、(**)の部分。

ここを

message_ = messageString

としてしまうと、getInstanceメソッドが返すインスタンスMessageプロパティの値がデフォルト値になってしまう。

このカラクリがわからず、しばらくハマってしまった……。

使ってみる

次のコードで実験

リスト3 標準モジュール
Public Sub disposable01()
  Dim greeterMan2 As HelloWorld
  Set greeterMan2 = HelloWorld.getInstance("ち~んw")
  greeterMan2.sayHello
End Sub

こいつを実行すると、

f:id:akashi_keirin:20190429112632j:plain

ちゃんと引数を渡してインスタンス化したような結果が得られた。

おわりに

何か面白いことに使えないかなあ。