インターフェイスの実装

提供: Appmethod Topics
移動先: 案内検索

オブジェクト インターフェイス:インデックス への移動

いったんインターフェイスを宣言したら、それを使用するにはクラスで実装する必要があります。クラスで実装されるインターフェイスは、クラスの宣言でクラスの親の名前の後に指定されます。

クラス宣言

宣言は次のような形式になります。

type className = class (ancestorClass, interface1, ..., interfaceN)
	memberList
end;

次に示すのはその例です。

type
  TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)
    // ...
  end;

これは、IMalloc インターフェイスと IErrorInfo インターフェイスを実装する TMemoryManager というクラスを宣言しています。クラスでインターフェイスを実装する場合は、そのインターフェイスで宣言されている各メソッドを実装(または各メソッドの実装を継承)する必要があります。

次に示すのは System.TInterfacedObject の宣言です。

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;

TInterfacedObjectIInterface インターフェイスを実装しています。したがって、TInterfacedObject では、IInterface の 3 つのメソッドをそれぞれ宣言および実装しています。

インターフェイスを実装しているクラスは、基底クラスとして使用することもできます。ちなみに、上記の最初の例では、TMemoryManagerTInterfacedObject の直接の下位クラスとして宣言しています。どのインターフェイスも 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;

IMallocAlloc メソッドと Free メソッドを TMemoryManagerAllocate メソッドと 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;

関連項目