VBAでインタフェースを使ってみた
インタフェースを用いたポリモーフィズムをやってみた
立山秀利さんが著書の中で使っていた音楽プレーヤのたとえが私にとっては一番分かりやすかったので、それをVBAでやってみる。
方針としては、
- 「RecordPlayer」クラス、「CDPlayer」クラス、「MP3Player」クラスの3つのクラスを作る
- 3つのクラスに共通する「音楽を再生する」という機能を「play」メソッドとして括り出す
- 「IMusicPlayer」インタフェースに「play」メソッドを定義する
- 「RecordPlayer」、「CDPlayer」、「MP3Player」の各クラスで「IMusicPlayer」インタフェースを実装する
- 「RecordPlayer」、「CDPlayer」、「MP3Player」の各クラスで「play」メソッドを独自に定義する
これでポリモーフィズムが実現できるはず。
イメージとしては、
音楽を再生する方法はどうあれ、ともかく「play」と命令すればそれぞれのオブジェクトがそれぞれのやり方で音楽を再生する
といったところか。
インタフェースの準備
リスト1 インタフェース「IMusicPlayer」
'クラスモジュール' 'オブジェクト名「IMusicPlayer」' Option Explicit 'Fields' Private Name_ As String 'Accessor' Public Property Get Name() As String Name = Name_ End Property 'Methods' Public Sub play() End Sub
「Name」というプロパティ(フィールド)を持たせていること、あとは「play」というメソッドを持たせているだけ。
特に、メソッドについては、名前を定義しているだけで処理の中身は空っぽ。
具体的な処理は、このインタフェースを実装する各クラスで独自に定義するのだから、これでいいのだ(ですよね?)。
各クラスでのインタフェースの実装
リスト2-1 「RecordPlayer」クラス
'クラスモジュール' 'オブジェクト名「RecordPlayer」' Option Explicit Implements IMusicPlayer '……(1)' 'Fields' Private IMusicPlayer_Name_ As String '……(2)' 'Accessor' Public Property Get IMusicPlayer_Name() As String '……(3)' IMusicPlayer_Name = IMusicPlayer_Name_ End Property 'Methods' Public Sub init(ByVal n As String) IMusicPlayer_Name_ = n '……(4)' End Sub Public Sub IMusicPlayer_play() '……(5)' Debug.Print "レコードを再生するぜ~♪" & vbCrLf & _ "針が飛ぶから、暴れるんじゃねーぞw" End Sub
ほとんど通常のクラスモジュールと同じ書き方なんだけど、至る所に「IMusicPlayer」というインタフェース名が出てくるところがポイント。
まず(1)、
Implements IMusicPlayer
宣言セクションでこう書く。変なの。
(2)と(3)、フィールド・アクセサの設定も、
Private IMusicPlayer_Name_ As String Public Property Get IMusicPlayer_Name() As String IMusicPlayer_Name = IMusicPlayer_Name_ End Property
「Name」というフィールド(プロパティ)名にいちいち「IMusicPlayer」をスネーク記法で付けないといけない。これも何だかなー。
(4)の
IMusicPlayer_Name_ = n
では、擬似コンストラクタのinitメソッドの引数を受け取っている。当然ここにも「IMusicPlayer」が……。
あと、(5)のメソッド名の定義、
Public Sub IMusicPlayer_play()
にもやっぱり「IMusicPlayer」……。
仕様とはいえ、何だか美しくないんだよなー……。
あと、「CDPlayer」、「MP3Player」についても、やり方は全く同じなので、リストだけ載っけときます。
リスト2-2 「CDPlayer」クラス
'クラスモジュール' 'オブジェクト名「CDPlayer」' Option Explicit Implements IMusicPlayer 'Fields' Private IMusicPlayer_Name_ As String 'Accessor' Public Property Get IMusicPlayer_Name() As String IMusicPlayer_Name = IMusicPlayer_Name_ End Property 'Methods' Public Sub init(ByVal n As String) IMusicPlayer_Name_ = n End Sub Public Sub IMusicPlayer_play() Debug.Print "CDを再生するぜ~♪" End Sub
リスト2-3 「MP3Player」クラス
'クラスモジュール' 'オブジェクト名「MP3Player」' Option Explicit Implements IMusicPlayer 'Fields' Private IMusicPlayer_Name_ As String 'Accessor' Public Property Get IMusicPlayer_Name() As String IMusicPlayer_Name = IMusicPlayer_Name_ End Property 'Methods' Public Sub init(ByVal n As String) IMusicPlayer_Name_ = n End Sub Public Sub IMusicPlayer_play() Debug.Print "MP3を再生するぜ~♪" End Sub
長ったらしくなってすまん。
まあ、これで3つのクラス全てに「IMusicPlayer」インタフェースを実装したことになる。
(「実装」の使い方、これで合ってるんだろうか……?)
ポリモーフィズムをやってみる
次のコードで各種音楽プレーヤを使ってみる。
Option Explicit Public Sub musicPlayerTest() 'インスタンス用変数を準備' '……(1)' Dim cdp As CDPlayer Dim rp As RecordPlayer Dim mp3p As MP3Player 'インスタンス生成&擬似コンストラクタ発動' '……(2)' Set rp = New RecordPlayer rp.init "レコードプレーヤー1号" Set cdp = New CDPlayer cdp.init "CDプレーヤ1号" Set mp3p = New MP3Player mp3p.init "ストロングマシン1号" 'インタフェース型配列に各インスタンスを格納' '……(3)' Dim iMPlayer(0 To 2) As IMusicPlayer '……(4)' Set iMPlayer(0) = rp '……(5)' Set iMPlayer(1) = cdp Set iMPlayer(2) = mp3p 'Nameプロパティの出力とplayメソッドの実行' '……(6)' Dim i As Integer For i = 0 To 2 With iMPlayer(i) Debug.Print .Name '……(7)' .play '……(8)' End With Next End Sub
(1)の後の3行
Dim cdp As CDPlayer Dim rp As RecordPlayer Dim mp3p As MP3Player
で各音楽プレーヤ用の変数を準備。
(2)の後の6行では、たとえば「RecordPlayer」クラスの場合、
Set rp = New RecordPlayer rp.init "レコードプレーヤー1号"
こんなふうに、インスタンスを生成した後、擬似コンストラクタinitメソッドでNameプロパティを設定している。
「CDPlayer」にしても「MP3Player」にしてもやっていることは同じ。
で、(3)からは、(4)の
Dim iMPlayer(0 To 2) As IMusicPlayer
インタフェース「IMusicPlayer」型の配列変数を用意して、
(5)からの
Set iMPlayer(0) = rp Set iMPlayer(1) = cdp Set iMPlayer(2) = mp3p
でそれぞれのクラスのインスタンスを配列に格納している。
後は、(6)の後の7行、
Dim i As Integer For i = 0 To 2 With iMPlayer(i) Debug.Print .Name '……(7)' .play '……(8)' End With Next
で、配列の各要素について、(7)で「Name」プロパティを表示させ、(8)でplayメソッドを実行している。
このとき、配列「iMPlayer」の各要素は、それぞれ別々のクラスのインスタンスなのに、全て同じ名前でプロパティやメソッドが呼び出せているというところがミソ。
実行結果
ほれ。同じメソッド名で呼び出したにもかかわらず、それぞれのクラスがそれぞれのやり方でplayメソッドを実行していることが分かる。
おわりに
正直、まだインタフェースの使いどころについてはピンときていないが、実際にコードを書いてみると、思ったより簡単だった。やっぱり、実際にコードを書いて動かしてみるというのが大事なんだなあ。
……と、ここまで書いてきてから気づいたんだが、
initメソッドもまとめてしまったらよかったんじゃね?
……orz
ちなみに、クラス側でちゃんとメソッドを置かないと、こんなふうに叱られる。