7.5 Interface delegation

Sometimes, the methods of an interface are implemented by a helper (or delegate) object, or the class instance has obtained an interface pointer for this interface and that should be used. This can be for instance when an interface must be added to a series of totally unrelated classes: the needed interface functionality is added to a separate class, and each of these classes uses an instance of the helper class to implement the functionality.

In such a case, it is possible to instruct the compiler that the interface is not implemented by the object itself, but actually resides in a helper class or interface. This can be done with the implements property modifier.

If the class has a pointer to the desired interface, the following will instruct the compiler that when the IMyInterface interface is requested, it should use the reference in the field:

type  
  IMyInterface = interface  
    procedure P1;  
  end;  
 
  TMyClass = class(TInterfacedObject, IMyInterface)  
  private  
    FMyInterface: IMyInterface; // interface type  
  public  
    property MyInterface: IMyInterface  
       read FMyInterface implements IMyInterface;  
  end;

The interface should not necessarily be in a field, any read identifier can be used.

If the interface is implemented by a delegate object, (a helper object that actually implements the interface) then it can be used as well with the implements keyword:

{$interfaces corba}  
type  
  IMyInterface = interface  
    procedure P1;  
  end;  
 
  // NOTE: Interface must be specified here  
  TDelegateClass = class(TObject, IMyInterface)  
  private  
    procedure P1;  
  end;  
 
  TMyClass = class(TInterfacedObject, IMyInterface)  
  private  
    FMyInterface: TDelegateClass; // class type  
    property MyInterface: TDelegateClass  
      read FMyInterface implements IMyInterface;  
  end;

Note that in difference with Delphi, the delegate class must explicitly specify the interface: the compiler will not search for the methods in the delegate class, it will simply check if the delegate class implements the specified interface.

It is possible to implement multiple interfaces using a single delegated object:

{$interfaces corba}  
type  
  IMyInterface = interface  
    procedure P1;  
  end;  
  IMyInterface1 = interface  
    procedure P2;  
  end;  
 
  // NOTE: Interface must be specified here  
  TDelegateClass = class(TObject, IMyInterface,IMyInterface1)  
  private  
    procedure P1;  
    procedure P2;  
  end;  
 
  TMyClass = class(TInterfacedObject, IMyInterface, IMyInterface1)  
  private  
    FMyInterface: TDelegateClass; // class type  
    property MyInterface: TDelegateClass  
      read FMyInterface implements IMyInterface,IMyInterface1;  
  end;

It is not possible to mix method resolution and interface delegation. That means, it is not possible to implement part of an interface through method resolution and implement part of the interface through delegation. The following attempts to implement IMyInterface partly through method resolution (P1), and partly through delegation. The compiler will not accept the following code:

{$interfaces corba}  
type  
  IMyInterface = interface  
    procedure P1;  
    procedure P2;  
  end;  
 
 
  TMyClass = class(TInterfacedObject, IMyInterface)  
    FI : IMyInterface;  
  protected  
    procedure IMyInterface.P1 = MyP1;  
    procedure MyP1;  
    property MyInterface: IMyInterface  read FI implements IMyInterface;  
  end;

The compiler will throw an error:

Error: Interface "IMyInterface" can't be delegated by "TMyClass",  
it already has method resolutions

However, it is possible to implement one interface through method resolution, and another through delegation:

{$interfaces corba}  
type  
  IMyInterface = interface  
    procedure P1;  
  end;  
 
  IMyInterface2 = interface  
    procedure P2;  
  end;  
 
  TMyClass = class(TInterfacedObject,  
                   IMyInterface, IMyInterface2)  
    FI2 : IMyInterface2;  
  protected  
    procedure IMyInterface.P1 = MyP1;  
    procedure MyP1;  
  public  
    property MyInterface: IMyInterface2  
       read FI2 implements IMyInterface2;  
  end;

Note that interface delegation can be used to specify that a class implements parent interfaces:

IGMGetFileName = interface(IUnknown)  
  ['{D3ECCB42-A563-4cc4-B375-79931031ECBA}']  
  function GetFileName: String; stdcall;  
  property FileName: String read GetFileName;  
end;  
 
IGMGetSetFileName = Interface(IGMGetFileName)  
  ['{ECFB879F-86F6-41a3-A685-0C899A2B5BCA}']  
  procedure SetFileName(const Value: String); stdcall;  
  property FileName: String read GetFileName write SetFileName;  
end;  
 
TIntfDelegator = class(TInterfacedObject, IGMGetFileName, IGMGetSetFileName)  
 protected  
  FGetSetFileName: IGMGetSetFileName;  
 public  
  constructor Create;  
  destructor Destroy; override;  
  property Implementor: IGMGetSetFileName read FGetSetFileName  
    implements IGMGetFileName, IGMGetSetFileName;  
end;