よく知られている通り、Visual Basic 6.0ではクラスの継承ができません。よって、抽象クラスもないしprotectedというアクセス指定子もありません。VB6プログラマにとって唯一インターフェース継承のみが残されていますが、これも機能は貧弱です。それではVB6プログラマはデザインパターンを意識したプログラミングはあきらめなければならないのでしょうか?
今回は、VB6のインターフェース継承の機能が貧弱であることを逆に利用してVB6で擬似的に抽象クラスを実装してしまおうというお話です。
インターフェースには本来実装が許されません。しかし、VB6では幸いにも?それができてしまいます。
以下のようなクラスを作成してみました。
' 擬似抽象クラス
' PseudoAbstractClass.cls(クラスモジュール)
Private m_SubClass As PseudoAbstractClass
' 内部変数(本当はprotectedにしたい)
Private m_Message As String
Private m_Count As Integer
' 子クラスを保持します
Public Property Set SubClass(value As PseudoAbstractClass)
Set m_SubClass = value
End Property
Public Sub Release()
' 後始末用
' ここでは実装しない
End Sub
Public Property Get Message() As String
Message = m_Message
End Property
Public Property Let Message(value As String)
m_Message = value
End Property
Public Property Get Count() As Integer
Count = m_Count
End Property
Public Property Let Count(value As Integer)
m_Count = value
End Property
' 擬似abstract関数。子クラスで実装
Public Function SuperClassAbstractFunction() As String
SuperClassAbstractFunction = _
m_SubClass.SuperClassAbstractFunction
End Function
Public Sub SuperClassAbstractSub()
m_SubClass.SuperClassAbstractSub
End Sub
Public Sub SuperClassSub()
Dim I As Integer
For I = 1 To m_Count
Debug.Print SuperClassAbstractFunction
Next I
Call SuperClassAbstractSub
End Sub
Visual Basic 6.0ではprotectedアクセス指定子がないので、仕方なくプロパティで代用します。メンバ変数はMessageと、Countです。
もう一つ、m_SubClassという変数があります。プロパティSubClassで取得した子クラスはここに格納されます。擬似抽象メソッドSuperClassAbstractFunction、SuperClassAbstractSubでは、m_SubClassの相当関数を実行します。擬似抽象メソッドは、これを継承したクラスが決定後初めて決まります。今は入れ物だけです。
メソッドSuperClassSubですが、ここでは抽象メソッドを使用して何を実行するかが書かれています。ここでは、SuperClassAbstractFunctionの戻り値をm_Count回デバッグウィンドウに表示した後、SuperClassAbstractSubを実行するといった単純なものです。擬似抽象クラスSuperClassAbstractFunctionと、SuperClassAbstractSubが何を行うかまだ決定していないことにご注意ください。
早速、この擬似抽象クラスを継承した子クラスを作成します。
' 子クラス
' SubClass1.cls(クラスモジュール)
Implements PseudoAbstractClass
Private m_BaseClass As PseudoAbstractClass
Private Sub Class_Initialize()
' 擬似抽象クラスのインスタンスを作り、そのSubClassプロパティに自分を代入する。
Set m_BaseClass = New PseudoAbstractClass
Set m_BaseClass.SubClass = Me
End Sub
Private Sub PseudoAbstractClass_Release()
' 後始末。使用後、必ず実行する。
Set m_BaseClass.SubClass = Nothing
Set m_BaseClass = Nothing
End Sub
Private Property Set PseudoAbstractClass_SubClass(value As PseudoAbstractClass)
' 実装しない
End Property
Private Property Let PseudoAbstractClass_Message(value As String)
m_BaseClass.Message = value
End Property
Private Property Get PseudoAbstractClass_Message() As String
PseudoAbstractClass_Message = m_BaseClass.Message
End Property
Private Property Let PseudoAbstractClass_Count(value As Integer)
m_BaseClass.Count = value
End Property
Private Property Get PseudoAbstractClass_Count() As Integer
PseudoAbstractClass_Count = m_BaseClass.Count
End Property
Private Sub PseudoAbstractClass_SuperClassSub()
m_BaseClass.SuperClassSub
End Sub
' ここより上は全て共通
' この下よりこのクラスの固有の実装(override)
Private Function PseudoAbstractClass_SuperClassAbstractFunction() As String
PseudoAbstractClass_SuperClassAbstractFunction = "*****" & m_BaseClass.Message & "*****"
End Function
Private Sub PseudoAbstractClass_SuperClassAbstractSub()
Debug.Print "******************************" & vbCrLf
Debug.Print "*********" & m_BaseClass.Message & "*********" & vbCrLf
Debug.Print "******************************" & vbCrLf
End Sub
冒頭にImplements PseudoAbstractClassとあります。これで、このクラスのオブジェクトをPseudoAbstractClassのSubClassプロパティに入れることができるようになります。このクラスのInitializeメソッド(コンストラクタ)で、擬似抽象クラスのインスタンスを作り、SubClassプロパティに自分(Me)を代入してしまいます。Message, Countプロパティはそのまま親クラスのものを引き継ぎます。PseudoAbstractClass_SuperClassSubは親クラスのものをそのまま引き継ぐため、m_BaseClassの相当関数を実行します。ここより上は全て共通です。PseudoAbstractClass_SuperClassAbstractFunction、PseudoAbstractClass_SuperClassAbstractSubで、このクラスに固有の実装(override)をします。メッセージに「*」の飾りをつけるだけという簡単なものです。
「ここより上は全て共通」コメントより上の部分をコピーして、SubClass2.clsというクラスを作り貼り付けます。その下に以下のような関数を作成します。
Private Function PseudoAbstractClass_SuperClassAbstractFunction() As String
PseudoAbstractClass_SuperClassAbstractFunction = "-----" & m_BaseClass.Message & "-----"
End Function
Private Sub PseudoAbstractClass_SuperClassAbstractSub()
Debug.Print "------------------------------" & vbCrLf
Debug.Print "---------" & m_BaseClass.Message & "---------" & vbCrLf
Debug.Print "------------------------------" & vbCrLf
End Sub
「*」が「-」に変わっただけです(^^;。以下は使用例です。
' Form1.frm(フォームモジュール)
Private Sub Command1_Click()
Dim AbstractBaseClass1 As PseudoAbstractClass
Set AbstractBaseClass1 = New SubClass1
AbstractBaseClass1.Message = "継承クラス1"
AbstractBaseClass1.Count = 2
AbstractBaseClass1.SuperClassSub
' 後始末は必ず実行する
AbstractBaseClass1.Release
Dim AbstractBaseClass2 As PseudoAbstractClass
Set AbstractBaseClass2 = New SubClass2
AbstractBaseClass2.Message = "継承クラス2"
AbstractBaseClass2.Count = 3
AbstractBaseClass2.SuperClassSub
AbstractBaseClass2.Release
End Sub
出力は以下の通りです。
*****継承クラス1*****
*****継承クラス1*****
******************************
*********継承クラス1*********
******************************
-----継承クラス2-----
-----継承クラス2-----
-----継承クラス2-----
------------------------------
---------継承クラス2---------
------------------------------
一見、子クラスのメンテが大変のように見えますが、擬似抽象メソッドの実装を除きほとんど共通ですので、一個作成、変更すれば後はコピペでいけます。
基本的な考え方は、親クラスで入れ物を用意しておき、子クラスで入れ物に実装することです。新しい実装がほしくなれば、SubClass1をじゃんじゃんコピーして擬似抽象メソッドの中だけ書き換えればよいわけです。
VBのInterfaceは、一回継承したものを親クラスとしてさらに継承することはできません。その代わり、一度に複数のInterfaceを継承することができます。
この考え方でVB6で色々なデザインパターンを試してみると面白そうです。できないもの、複雑すぎて実用性のないものも出てくるとは思いますが、「継承」について基本に戻って考えさせてくれる良い機会になるかもしれません。