Search This Blog

Thursday, July 13, 2023

Implementing Interfaces by Delegation in Delphi!

Implementing Interfaces by Delegation

Is it possible at all?😮

The implements directive allows you to delegate the implementation of an interface to a property in the implementing class. For example:


  property MyInterface: IMyInterface read FMyInterface implements IMyInterface;

Declares a property called MyInterface that implements the interface IMyInterface.

The implements directive must be the last specifier in the property declaration and can list more than one interface, separated by commas. The delegate property:

  • Must be of a class or interface type.
  • Cannot be an array property or have an index specifier.
  • Must have a read specifier. If the property uses a read method, that method must use the default register calling convention and cannot be dynamic (though it can be virtual) or specify the message directive.

The class you use to implement the delegated interface should derive from System.TAggregatedObject.

Delegating to an Interface-Type Property

If the delegate property is of an interface type, that interface, or an interface from which it derives, must occur in the ancestor list of the class where the property is declared. The delegate property must return an object whose class completely implements the interface specified by the implements directive, and which does so without method resolution clauses. For example:

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;

Delegating to a Class-Type Property

If the delegate property is of a class type, that class and its ancestors are searched for methods implementing the specified interface before the enclosing class and its ancestors are searched. Thus it is possible to implement some methods in the class specified by the property, and others in the class where the property is declared. Method resolution clauses can be used in the usual way to resolve ambiguities or specify a particular method. An interface cannot be implemented by more than one class-type property. For example:

type
  IMyInterface = interface
    procedure P1;
    procedure P2;
  end;
  TMyImplClass = class
    procedure P1;
    procedure P2;
  end;
  TExternalClass = class(TInterfacedObject, IMyInterface)
  private
    FMyImplClass: TMyImplClass;
  public  
    procedure IMyInterface.P1 = MyP1AlternativeProcedure;
    procedure MyP1AlternativeProcedure;
    
    property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
  end;
  
  procedure TMyImplClass.P1;
  begin
    Writeln('P1 called!');
  end;
  
  procedure TMyImplClass.P2;
  begin
    Writeln('P2 called!');
  end;
  
  procedure TExternalClass.MyP1AlternativeProcedure;
  begin
    Writeln('MyP1AlternativeProcedure called!');
  end;

var
  LvMyExternalClass: TMyClass;
  LvMyInterface: IMyInterface;
begin
  LvMyExternalClass := TExternalClass.Create;
  LvMyExternalClass.FMyImplClass := TMyImplClass.Create;
  
  LvMyInterface := LvMyExternalClass;
  LvMyInterface.P1;  // calls TMyClass.MyP1AlternativeProcedure;
  LvMyInterface.P2;  // calls TImplClass.P2;
  Readln;
end;

You can remove the following lines from TExternalClass and it's still implementing the interface correctly and compilable!

    procedure IMyInterface.P1 = MyP1AlternativeProcedure;
    procedure MyP1AlternativeProcedure; // and its implementation!

Here is another console-type application sample to make it more clear:


program InterfaceDelegationDemo;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  // Interface definition
  IFirstInterface = interface
    procedure FirstMethod;
  end;

  ISecondInterface = interface
    procedure SecondMethod;
  end;

  // Implementation of the first interface
  TFirstInterfaceImplementation = class(TInterfacedObject, IFirstInterface)
    procedure FirstMethod;
  end;

  // Implementation of the second interface using delegation
  TSecondInterfaceImplementation = class(TInterfacedObject, ISecondInterface)
  private
    FFirstInterfaceImpl: IFirstInterface;
  public
    constructor Create(AFirstInterfaceImpl: IFirstInterface);
    procedure SecondMethod;
  end;

{ TFirstInterfaceImplementation }

procedure TFirstInterfaceImplementation.FirstMethod;
begin
  Writeln('FirstMethod called');
end;

{ TSecondInterfaceImplementation }

constructor TSecondInterfaceImplementation.Create(AFirstInterfaceImpl: IFirstInterface);
begin
  FFirstInterfaceImpl := AFirstInterfaceImpl;
end;

procedure TSecondInterfaceImplementation.SecondMethod;
begin
  Writeln('SecondMethod called');
  FFirstInterfaceImpl.FirstMethod; // Delegating the method call to the first interface implementation
end;

var
  FirstInterfaceImpl: IFirstInterface;
  SecondInterfaceImpl: ISecondInterface;
begin
  FirstInterfaceImpl := TFirstInterfaceImplementation.Create;
  SecondInterfaceImpl := TSecondInterfaceImplementation.Create(FirstInterfaceImpl);

  SecondInterfaceImpl.SecondMethod;
  Readln;
end.


In this code, we have two interfaces, IFirstInterface and ISecondInterface. The TFirstInterfaceImplementation class implements the first interface, and the TSecondInterfaceImplementation class implements the second interface using delegation.

The TSecondInterfaceImplementation class takes an instance of IFirstInterface as a constructor parameter, which represents the first interface implementation. It stores this instance in the FFirstInterfaceImpl field.

When the SecondMethod of TSecondInterfaceImplementation is called, it first prints a message indicating that the method has been called. Then, it delegates the method call to the FirstMethod of the first interface implementation by calling FFirstInterfaceImpl.FirstMethod.

By using this delegation approach, you can separate the implementation of each interface and reuse existing implementations by passing them as parameters to other interface implementations.

No comments:

Post a Comment