1. Implementsとは?
Implementsは、あるクラスが、インターフェースとして作成された別のクラスで定義されたメソッドを必ず実装することを宣言するための機能です。
Implementsはクラスで扱う仕組みのため、標準モジュールでは利用できません。
他のプログラミング言語の「インターフェース」に似ていますが、VBAには厳密なインターフェース機能はないため、「インターフェース役となるクラスを作り、それをImplementsで実装する」という形で代用します。
以下では、Implementsの使い方と、インターフェースを使うことでどういう利点があるのかを説明します。
2. インターフェースの実装方法とImplementsの使い方
Implementsを使うことで何がいいのか、という話は後述します。まずは使い方の説明をします。
Implementsの使うには、少なくともクラスモジュールを2つ作成する必要があります。インターフェースとなるクラス(インターフェース役)と、そのインターフェースクラスを使うクラス(実装クラス)です。
インターフェース役:出力用インターフェース:InterfacePrint.cls
1 2 3 4 5 |
Option Explicit '// メソッド名だけ定義 Public Sub Print() End Sub |
実装クラス:PDF出力クラス:PDFPrint.cls
1 2 3 4 5 6 7 8 9 10 |
Option Explicit '// インターフェース宣言 Implements InterfacePrint '// インターフェース名_メソッド名 Private Sub InterfacePrint_Print() '// 処理を書く Debug.Print "Print run" End Sub |
作成するクラス | 内容 |
---|---|
インターフェース役 | メソッド名だけを書きます。メソッド内の実際の処理は書きません。名前だけです。
Implementsで利用するためにはメソッドやプロパティに「Public」を付ける必要があります。 |
実装クラス | 「Implements インターフェースとなるクラス」と記述して、「Private インターフェース名_メソッド名」の形でインターフェースに定義されている中身のないメソッド(処理を書いていない宣言のみのメソッド)の処理を実装します。
メソッドをPrivateにすることでクラスを直接見たときに隠蔽して利用できないようにし、インターフェースを介さないと見えないようにします。 メソッドの実装をしていない場合は、実行時に「コンパイルエラー:オブジェクトモジュールにはインターフェイス’メソッド名’用の’インターフェース名’が必要です。」になります。 なお、インターフェース役のメソッドの実装とは別に、通常のメソッドを書くことも可能です。 プロパティの場合は「Private Property Get/Let/Set インターフェース名_プロパティ名」になります。 |
3. Implementsを使った単純なコード例
コード構成以外のことが入ってくると分かりにくくなるので、あえて無機質な名前のサンプルコードにしています。
Implementsを使うと外部からはインターフェースメソッドを通じてアクセスすることにより、クラスの実装を隠蔽できる、という利点があります。
(1) インターフェースとなるクラス:InterfaceSample.cls
このクラスではメソッド名だけを書きます。実際の処理は書きません。メソッド名のみで中身を空のまま定義する理由は、実装はクラス側に任せるためです。ここではAAAメソッドとBBBメソッドの2つを書いていますが、いくつ書いても構いません。1つでも複数のメソッドでも作成できます。
Implementsで利用するためにはメソッドに「Public」を付ける必要があります。
1 2 3 4 5 6 7 8 9 |
Option Explicit '// メソッド名だけ定義 Public Sub AAA() End Sub '// メソッド名だけ定義 Public Sub BBB() End Sub |
(2) 実装クラス:ClassSample.cls
インターフェースを使う宣言として「Implements インターフェース名」を書きます。インターフェース役に書いてある空メソッドは実装クラスに必ず実装しなければなりません。実装する際には「Private インターフェース名_メソッド名」の形で作成します。Publicにすると直接実装クラスを利用した場合にメソッドが利用できるためPrivateにしてインターフェースを介してでないと利用できないようにします。
実装していない場合は実行時に「コンパイルエラー:オブジェクトモジュールにはインターフェイス’AAA’用の’InterfaceSample’が必要です。」とエラーダイアログが出ます。
実装クラスでは、インターフェースで定義されたメソッド以外に、独自のメソッド(ここでは CCC)を追加することもできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Option Explicit '// インターフェース宣言 Implements InterfaceSample '// InterfaceSample.AAAの実装(インターフェース名_メソッド名) Private Sub InterfaceSample_AAA() '// 処理を書く Debug.Print "AAA" End Sub '// InterfaceSample.BBBの実装(インターフェース名_メソッド名) Private Sub InterfaceSample_BBB() '// 処理を書く Debug.Print "BBB" End Sub '// 通常のメソッド Public Sub CCC() Debug.Print "CCC" End Sub |
4. 呼び出し側のコード例
上記のコードを標準モジュールなどから呼び出す場合の例をコードで説明します。
Debug.Printでイミディエントウィンドウ(Ctrl + Gで表示)に出力しています。
例1(推奨):インターフェースを使った呼び出し
インターフェースを意図した呼び出し例です。インターフェースを使っている場合はこちらの書き方が望ましいです。
インターフェース型(InterfaceSample)を宣言し、実装クラス(ClassSample)でNewでのインスタンスを作成し、インターフェース(InterfaceSample)のメソッド名で処理を呼び出しています。変数cはインターフェースを対象としているため、ClassSampleのCCCは呼び出せません。
インターフェースを使うことで、実装の詳細に依存せずに呼び出すことができ、保守性・拡張性に優れた(処理の変更があっても対応がよりしやすい)コードになります。
1 2 3 4 5 6 7 8 9 |
Option Explicit Sub testsample1() Dim c As InterfaceSample '// ★下のコードとはここが違う Set c = New ClassSample c.AAA '// "AAA"とイミディエイトウィンドウに出力 c.BBB '// "BBB"とイミディエイトウィンドウに出力 End Sub |
実行結果(イミディエイトウィンドウに出力)
AAA
BBB
例2(非推奨):実装クラスを直接呼出し
実装クラス(ClassSample)を直接呼び出す例です。インターフェースでの実装をしているのにそれを経由せずに直接呼び出しているため、インターフェースで抽象化した意味が無くなっています。
実装クラスでは、「Private インターフェース名_メソッド名」で実装しているため、その名前で呼び出そうとしてもPrivate(非公開)のため参照できずにエラーになります。
「Private」を「Public」にするとエラーは解消されますが、インターフェースを利用している場合はこのメソッドを呼び出すような実装をすることはインターフェースを使っている意味がなくなるためNGです。
実装クラスのPublicの独自メソッド(CCC)も実行できます。
インターフェースを経由せずに実装クラスを直接呼び出すと、実装の変更がそのまま呼び出し側に影響しやすくなり、インターフェースを設計した意味を損ないます。
1 2 3 4 5 6 7 8 |
Sub testsample2() Dim c As ClassSample '// ★上のコードとはここが違う Set c = New ClassSample '//c.InterfaceSample_AAA '// Privateのため見えない。実行するとエラーになる。 '//c.InterfaceSample_BBB '// Privateのため見えない。実行するとエラーになる。 c.CCC '// "CCC"とイミディエイトウィンドウに出力 End Sub |
実行結果(イミディエイトウィンドウに出力)
CCC
5. インターフェースにプロパティを定義する簡単なコード例
インターフェースにはメソッドだけでなくプロパティも定義できます。
メソッドと同様で、インターフェース型をImplementsしたクラスでは、プロパティも実装する必要があります。
インターフェースでGet / Let / Setの空メソッドを定義します。空のまま定義する理由は、メソッドと同様で実装はクラス側に任せるためです。Get / Let /Set の考え方はクラスのプロパティと同じです。
Implementsで利用するためにはプロパティに「Public」を付ける必要があります。
以下のコードではStirng型を使っているため省略していますが、オブジェクト型のプロパティを扱う場合は、Property Set を使用します。
(1) インターフェースモジュール名: InterfacePropertySample.cls
1 2 3 4 5 6 7 8 9 |
Option Explicit '// 更新プロパティ Property Get Name() As String End Property '// 参照プロパティ Property Let Name(ByVal value As String) End Property |
(2) 実装クラスモジュール名: ClassPropertySample.cls
インターフェースメソッドの場合と同様に、プロパティの場合も「Private Property Get/Let/Set インターフェース名_プロパティ名」の形式で実装します。Privateにするのはメソッドのときと同じ理由です。
ここではNameプロパティがインターフェース役のものと、クラス独自のものを作っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
Option Explicit '// インターフェース定義 Implements InterfacePropertySample '// 内部変数 Private m_name As String '// InterfacePropertySample.Name の実装(Get) Private Property Get InterfacePropertySample_Name() As String InterfacePropertySample_Name = m_name Debug.Print "Get InterfacePropertySample_Name()" End Property '// InterfacePropertySample.Name の実装(Let) Private Property Let InterfacePropertySample_Name(ByVal value As String) m_name = value Debug.Print "Let InterfacePropertySample_Name()" End Property '// このクラス独自のプロパティ(直接使う場合はこちらでもよい) Public Property Get Name() As String Name = m_name Debug.Print "Get Name()" End Property Public Property Let Name(ByVal value As String) m_name = value Debug.Print "Let Name()" End Property |
6. 呼び出し側のコード例(プロパティ)
例1(推奨)
インターフェースを意図した呼び出し例です。
p.Nameの部分は、ClassPropertySample の InterfacePropertySample_Name プロパティが内部的に呼び出されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Sub testsample3() Dim p As InterfacePropertySample '// ★下のコードとはここが違う Set p = New ClassPropertySample '// ClassPropertySampleのGet InterfacePropertySample_Name() での参照 Debug.Print p.Name '// 空が出力される '// ClassPropertySampleのLet InterfacePropertySample_Name() での更新 p.Name = "DDD" '// ClassPropertySampleのGet InterfacePropertySample_Name() での参照 Debug.Print p.Name '// "DDD"が出力される End Sub |
実行結果(イミディエイトウィンドウに出力)
Get InterfacePropertySample_Name()
Let InterfacePropertySample_Name()
Get InterfacePropertySample_Name()
DDD
例2(非推奨)
実装クラス(ClassSample)を意識した呼び出し例です。
実装クラスを直接参照すると、将来変更があった場合に呼び出し側のコードを修正する必要があるため、保守性が下がります。そのためこの書き方は非推奨です。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Sub testsample4() Dim p As ClassPropertySample '// ★上のコードとはここが違う Set p = New ClassPropertySample '// ClassPropertySampleのGet Name() での参照 Debug.Print p.Name '// 空が出力される '// ClassPropertySampleのLet Name() での参照 p.Name = "EEE" '// ClassPropertySampleのGet Name() での参照 Debug.Print p.Name '// "EEE"が出力される End Sub |
実行結果(イミディエイトウィンドウに出力)
Get Name()
Let Name()
Get Name()
EEE
7. 注意事項・制限事項
- 1つのクラスで複数のインターフェースを Implements することは可能ですが、継承関係は1階層のみです。
- インターフェースのプロパティやメソッドはPublicで定義し、実装クラスではPrivateで実装する必要があります。Publicでも動きますがその場合はインターフェースとは無関係の実装クラス独自メソッドとして解釈されます。Public インターフェース名_メソッド名 での命名は「実装クラスにインターフェースのメソッドと同じ名前で無理やり違うメソッドを作った」という状況になるため設計としては当然バグです。
- インターフェースで定義できるのは、メソッド名やプロパティ名のみ(定義のみ)です。実装(中身)は書けません。
- インターフェースを通じてプロパティやメソッドを使用するには、インターフェース型の変数を扱う必要があります。クラス型で使うと実装クラスの独自実装が優先されます。
- 実装クラス内で同じプロパティ名を持つ独自のメンバを作成すると、混乱やバグの原因になる可能性があるため注意が必要です。
- 例外処理やエラー管理は、インターフェースでは定義できないため、呼び出し側で適切に処理する必要があります。