1. Implementsとは?

Implementsは、あるクラスが、インターフェースとして作成された別のクラスで定義されたメソッドを必ず実装することを宣言するための機能です。

Implementsはクラスで扱う仕組みのため、標準モジュールでは利用できません。

他のプログラミング言語の「インターフェース」に似ていますが、VBAには厳密なインターフェース機能はないため、「インターフェース役となるクラスを作り、それをImplementsで実装する」という形で代用します。

以下では、Implementsの使い方と、インターフェースを使うことでどういう利点があるのかを説明します。

2. インターフェースの実装方法とImplementsの使い方

Implementsを使うことで何がいいのか、という話は後述します。まずは使い方の説明をします。

Implementsの使うには、少なくともクラスモジュールを2つ作成する必要があります。インターフェースとなるクラス(インターフェース役)と、そのインターフェースクラスを使うクラス(実装クラス)です。

インターフェース役:出力用インターフェース:InterfacePrint.cls

実装クラス:PDF出力クラス:PDFPrint.cls

作成するクラス 内容
インターフェース役 メソッド名だけを書きます。メソッド内の実際の処理は書きません。名前だけです。

Implementsで利用するためにはメソッドやプロパティに「Public」を付ける必要があります。

実装クラス 「Implements インターフェースとなるクラス」と記述して、「Private インターフェース名_メソッド名」の形でインターフェースに定義されている中身のないメソッド(処理を書いていない宣言のみのメソッド)の処理を実装します。

メソッドをPrivateにすることでクラスを直接見たときに隠蔽して利用できないようにし、インターフェースを介さないと見えないようにします。

メソッドの実装をしていない場合は、実行時に「コンパイルエラー:オブジェクトモジュールにはインターフェイス’メソッド名’用の’インターフェース名’が必要です。」になります。

なお、インターフェース役のメソッドの実装とは別に、通常のメソッドを書くことも可能です。

プロパティの場合は「Private Property Get/Let/Set インターフェース名_プロパティ名」になります。

3. Implementsを使った単純なコード例

コード構成以外のことが入ってくると分かりにくくなるので、あえて無機質な名前のサンプルコードにしています。

Implementsを使うと外部からはインターフェースメソッドを通じてアクセスすることにより、クラスの実装を隠蔽できる、という利点があります。

(1) インターフェースとなるクラス:InterfaceSample.cls

このクラスではメソッド名だけを書きます。実際の処理は書きません。メソッド名のみで中身を空のまま定義する理由は、実装はクラス側に任せるためです。ここではAAAメソッドとBBBメソッドの2つを書いていますが、いくつ書いても構いません。1つでも複数のメソッドでも作成できます。

Implementsで利用するためにはメソッドに「Public」を付ける必要があります。

(2) 実装クラス:ClassSample.cls

インターフェースを使う宣言として「Implements インターフェース名」を書きます。インターフェース役に書いてある空メソッドは実装クラスに必ず実装しなければなりません。実装する際には「Private インターフェース名_メソッド名」の形で作成します。Publicにすると直接実装クラスを利用した場合にメソッドが利用できるためPrivateにしてインターフェースを介してでないと利用できないようにします。

実装していない場合は実行時に「コンパイルエラー:オブジェクトモジュールにはインターフェイス’AAA’用の’InterfaceSample’が必要です。」とエラーダイアログが出ます。

実装クラスでは、インターフェースで定義されたメソッド以外に、独自のメソッド(ここでは CCC)を追加することもできます。

4. 呼び出し側のコード例

上記のコードを標準モジュールなどから呼び出す場合の例をコードで説明します。

Debug.Printでイミディエントウィンドウ(Ctrl + Gで表示)に出力しています。

例1(推奨):インターフェースを使った呼び出し

インターフェースを意図した呼び出し例です。インターフェースを使っている場合はこちらの書き方が望ましいです。

インターフェース型(InterfaceSample)を宣言し、実装クラス(ClassSample)でNewでのインスタンスを作成し、インターフェース(InterfaceSample)のメソッド名で処理を呼び出しています。変数cはインターフェースを対象としているため、ClassSampleのCCCは呼び出せません。

インターフェースを使うことで、実装の詳細に依存せずに呼び出すことができ、保守性・拡張性に優れた(処理の変更があっても対応がよりしやすい)コードになります。

実行結果(イミディエイトウィンドウに出力)
AAA
BBB

例2(非推奨):実装クラスを直接呼出し

実装クラス(ClassSample)を直接呼び出す例です。インターフェースでの実装をしているのにそれを経由せずに直接呼び出しているため、インターフェースで抽象化した意味が無くなっています。

実装クラスでは、「Private インターフェース名_メソッド名」で実装しているため、その名前で呼び出そうとしてもPrivate(非公開)のため参照できずにエラーになります。

「Private」を「Public」にするとエラーは解消されますが、インターフェースを利用している場合はこのメソッドを呼び出すような実装をすることはインターフェースを使っている意味がなくなるためNGです。

実装クラスのPublicの独自メソッド(CCC)も実行できます。

インターフェースを経由せずに実装クラスを直接呼び出すと、実装の変更がそのまま呼び出し側に影響しやすくなり、インターフェースを設計した意味を損ないます。

実行結果(イミディエイトウィンドウに出力)
CCC

5. インターフェースにプロパティを定義する簡単なコード例

インターフェースにはメソッドだけでなくプロパティも定義できます。

メソッドと同様で、インターフェース型をImplementsしたクラスでは、プロパティも実装する必要があります。

インターフェースでGet / Let / Setの空メソッドを定義します。空のまま定義する理由は、メソッドと同様で実装はクラス側に任せるためです。Get / Let /Set の考え方はクラスのプロパティと同じです。

Implementsで利用するためにはプロパティに「Public」を付ける必要があります。

以下のコードではStirng型を使っているため省略していますが、オブジェクト型のプロパティを扱う場合は、Property Set を使用します。

(1) インターフェースモジュール名: InterfacePropertySample.cls

(2) 実装クラスモジュール名: ClassPropertySample.cls

インターフェースメソッドの場合と同様に、プロパティの場合も「Private Property Get/Let/Set インターフェース名_プロパティ名」の形式で実装します。Privateにするのはメソッドのときと同じ理由です。

ここではNameプロパティがインターフェース役のものと、クラス独自のものを作っています。

6. 呼び出し側のコード例(プロパティ)

例1(推奨)

インターフェースを意図した呼び出し例です。

p.Nameの部分は、ClassPropertySample の InterfacePropertySample_Name プロパティが内部的に呼び出されます。

実行結果(イミディエイトウィンドウに出力)
Get InterfacePropertySample_Name()

Let InterfacePropertySample_Name()
Get InterfacePropertySample_Name()
DDD

例2(非推奨)

実装クラス(ClassSample)を意識した呼び出し例です。

実装クラスを直接参照すると、将来変更があった場合に呼び出し側のコードを修正する必要があるため、保守性が下がります。そのためこの書き方は非推奨です。

実行結果(イミディエイトウィンドウに出力)
Get Name()

Let Name()
Get Name()
EEE

7. 注意事項・制限事項

  1. 1つのクラスで複数のインターフェースを Implements することは可能ですが、継承関係は1階層のみです。
  2. インターフェースのプロパティやメソッドはPublicで定義し、実装クラスではPrivateで実装する必要があります。Publicでも動きますがその場合はインターフェースとは無関係の実装クラス独自メソッドとして解釈されます。Public インターフェース名_メソッド名 での命名は「実装クラスにインターフェースのメソッドと同じ名前で無理やり違うメソッドを作った」という状況になるため設計としては当然バグです。
  3. インターフェースで定義できるのは、メソッド名やプロパティ名のみ(定義のみ)です。実装(中身)は書けません。
  4. インターフェースを通じてプロパティやメソッドを使用するには、インターフェース型の変数を扱う必要があります。クラス型で使うと実装クラスの独自実装が優先されます。
  5. 実装クラス内で同じプロパティ名を持つ独自のメンバを作成すると、混乱やバグの原因になる可能性があるため注意が必要です。
  6. 例外処理やエラー管理は、インターフェースでは定義できないため、呼び出し側で適切に処理する必要があります。