インターフェイスの実装
目次
いったんインターフェイスを宣言したら、それを使用するにはクラスで実装する必要があります。クラスで実装されるインターフェイスは、クラスの宣言でクラスの親の名前の後に指定されます。
クラス宣言
宣言は次のような形式になります。
type className = class (ancestorClass, interface1, ..., interfaceN) memberList end;
例:
type TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo) // ... end;
これは、インターフェイス IMalloc
および IErrorInfo
を実装する、TMemoryManager
という名のクラスを宣言しています。クラスはインターフェイスを実装する際、そのインターフェイスで宣言されている各メソッドを実装(もしくは、実装を継承)しなければなりません。
次に示すのは System.TInterfacedObject
の宣言です(Windows の場合。その他のプラットフォームでの宣言は、少し異なります)。
type TInterfacedObject = class(TObject, IInterface) protected FRefCount: Integer; function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; public procedure AfterConstruction; override; procedure BeforeDestruction; override; class function NewInstance: TObject; override; property RefCount: Integer read FRefCount; end;
TInterfacedObject
は、IInterface
インターフェイスを実装しています。TInterfacedObject
では、3 つの IInterface
のメソッドをそれぞれ宣言および実装しています。
インターフェイスを実装しているクラスは、基底クラスとして使用することもできます。ちなみに、上記の最初の例では、TMemoryManager
を TInterfacedObject
の直接の下位クラスとして宣言しています。どのインターフェイスも IInterface
を継承し、それらのインターフェイスを実装するクラスでは、QueryInterface
、_AddRef
、_Release
の各メソッドを実装する必要があります。System
ユニットの TInterfacedObject
ではこれらのメソッドを実装しているため、インターフェイスを実装する他のクラスの派生元となる便利な基底クラスになっています。
インターフェイスを実装したらそのメソッドは、実装クラス内の、同じ戻り値型、同じ呼び名、同じ数のパラメータ、そして順序まで一致する型付きパラメータを持つメソッドに、それぞれマッピングされます。デフォルトでは、インターフェイスの各メソッドは、実装クラス内の同じ名前のメソッドにマッピングされます。
メソッドの解決句
メソッド解決句をクラス宣言に入れることで、この名前を基準としたデフォルトのマッピング方式を上書きすることができます。クラスが同じ名前を持つメソッドを持った、2 つ以上のインターフェイスを実装している場合、メソッド解決句を使用して、この名前の衝突を解決する必要があります。
メソッド解決句の形式は次のとおりです。
procedure interface.interfaceMethod = implementingMethod;
または
function interface.interfaceMethod = implementingMethod;
ここでの implementingMethod は、クラスまたはその親の 1 つで宣言されたメソッドです。implementingMethod は、後からクラス宣言で宣言されるメソッドにはできますが、他のモジュール内で宣言された親クラスの private メソッドにはなり得ません。
たとえば、次のクラス宣言では、
type TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo) function IMalloc.Alloc = Allocate; procedure IMalloc.Free = Deallocate; // ... end;
は、IMalloc
のメソッド Alloc
と Free
を、TMemoryManager
のメソッド Allocate
と Deallocate
にマップします。
メソッド解決句は、親クラスで導入されたマッピングは変更できません。
継承した実装の変更
下位クラスでは、特定のインターフェイス メソッドの実装内容を、その実装メソッドで上書きすることで変更することができます。これには、実装メソッドが、仮想または動的である必要があります。
また、上位クラスから継承したインターフェイス全体をクラスで再実装することもできます。その場合は、そのインターフェイスを下位クラスの宣言で再度記載する必要があります。以下に例を示します。
type IWindow = interface ['{00000115-0000-0000-C000-000000000146}'] procedure Draw; // ... end; TWindow = class(TInterfacedObject, IWindow) // TWindow implements IWindow pocedure Draw; // ... end; TFrameWindow = class(TWindow, IWindow) // TFrameWindow reimplements IWindow procedure Draw; // ... end;
インターフェイスを再実装すると、同じインターフェイスの継承した実装内容を隠ぺいします。このため、親クラスのメソッド解決句は、再実装されたインターフェイスでは無視されます。
委譲によるインターフェイスの実装
implements 指令により、インターフェイスの実装を実装クラスのプロパティに委譲することができます。以下に例を示します。
property MyInterface: IMyInterface read FMyInterface implements IMyInterface;
ここでは、インターフェイス IMyInterface
を実装する、プロパティ MyInterface
を宣言しています。
implements 指令は、プロパティ宣言の最後に指定する必要があり、複数のインターフェイスをコンマで区切って列挙することができます。デリゲート(委譲)プロパティには次の制約条件があります。
- クラス型またはインターフェイス型である。
- 配列プロパティにはできず、また、インデックス指定子も持てない。
- read 指定子と持つ。プロパティが read メソッドを使用する場合、そのメソッドは、デフォルトの register 呼び出し規約を使用しなければならず、また、動的にはできず(仮想にはできるが)、また、message 指令を指定することはできません。
デリゲートされたインターフェイスの実装に使用するクラスは、System.TAggregatedObject
から派生していなければなりません。
インターフェイス型プロパティへの委譲
デリゲート プロパティがインターフェイス型の場合、そのインターフェイスまたはその派生元のインターフェイスは、そのプロパティが宣言されているクラスの上位クラス リストに含まれていなければなりません。デリゲート プロパティでは、implements 指令で指定されたインターフェイスを完全実装しているクラスのオブジェクトを返す必要があり、それはメソッド解決句を使用せずに行います。以下に例を示します。
type IMyInterface = interface procedure P1; procedure P2; end; TMyClass = class(TObject, IMyInterface) FMyInterface: IMyInterface; property MyInterface: IMyInterface read FMyInterface implements IMyInterface; end; var MyClass: TMyClass; MyInterface: IMyInterface; begin MyClass := TMyClass.Create; MyClass.FMyInterface := ...// some object whose class implements IMyInterface MyInterface := MyClass; MyInterface.P1; end;
クラス型プロパティへの委譲
デリゲート プロパティがクラス型の場合、そのクラスおよびその上位クラスで、指定のインターフェイスを実装するメソッドが検索され、次に、そのプロパティを含んでいるクラスおよびその上位クラスが検索されます。このため、プロパティで指定されたクラスに一部のメソッドを実装し、他はプロパティが宣言されたクラスに実装することも可能です。メソッド解決句を通常の方法で使用して、あいまいさを解消したり特定のメソッドを指定することができます。1 つのインターフェイスを複数のクラス型プロパティで実装することはできません。以下に例を示します。
type IMyInterface = interface procedure P1; procedure P2; end; TMyImplClass = class procedure P1; procedure P2; end; TMyClass = class(TInterfacedObject, IMyInterface) FMyImplClass: TMyImplClass; property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface; procedure IMyInterface.P1 = MyP1; procedure MyP1; end; procedure TMyImplClass.P1; // ... procedure TMyImplClass.P2; // ... procedure TMyClass.MyP1; // ... var MyClass: TMyClass; MyInterface: IMyInterface; begin MyClass := TMyClass.Create; MyClass.FMyImplClass := TMyImplClass.Create; MyInterface := MyClass; MyInterface.P1; // calls TMyClass.MyP1; MyInterface.P2; // calls TImplClass.P2; end;