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