Composition Over Inheritance
Made Native
You know interfaces, and you know inheritance. Delphi's implements keyword bridges the gap, allowing a class to delegate the implementation of an interface to an internal property.
This enables Multiple Inheritance of Implementation (conceptually) without the "Diamond Problem" complexity, by aggregating separate implementation objects into a single host.
Architectural Insight
Instead of writing wrapper methods that simply call FInnerObj.Method, the compiler automatically routes QueryInterface calls for the specified interface to the property instance.
Architecture Trade-offs
Comparison: Standard Inheritance vs. Delegation (Implements)
The implements Directive
In standard Delphi interface implementation, the host class must implement every method defined in the interface. The implements directive modifies this behavior by telling the compiler:
"Do not look for the methods of this interface in my method table. Instead, delegate the implementation to this property."
When QueryInterface is called on the host for the specific interface ID (IID), the host returns the pointer to the object or interface held in the specified property. This allows you to mix in behaviors from other classes dynamically.
Syntax Anatomy
Click the interactive code tokens below to understand how the compiler wires the Virtual Method Table (VMT).
ILogger = interface
['{...}']
procedure Log(Msg: string);
end;
// The Host Class
THost = class(TInterfacedObject, ILogger)
private
FLoggerImpl: TMyLogger;
public
constructor Create;
property Logger: ILogger
read FLoggerImpl
implements ILogger;
end;
Architectural Patterns
Three powerful ways to leverage `implements` in production code.
The Reference Counting Trap
The biggest danger when using `implements` is the Circular Reference. If your Host holds a reference to the Delegate (Implementation), and the Delegate holds a reference back to the Host (which often happens implicitly in interfaces), you get a memory leak.
The Solution: TAggregatedObject
Standard TInterfacedObject maintains its own RefCount. TAggregatedObject delegates its RefCounting to a "Controller" (the Host).
- ✓ Host is the Controller.
-
✓
Delegate inherits from
TAggregatedObject. -
✓
Pass
Self(Host) to Delegate's constructor.
TMyDelegate = class(TAggregatedObject, IMyIntf)
...
end;
// Host Class
THost = class(TInterfacedObject, IMyIntf)
private
FImpl: TMyDelegate;
public
constructor Create;
property AsIntf: IMyIntf
read FImpl implements IMyIntf;
end;
// Construction
constructor THost.Create;
begin
// Pass Host (Self) as Controller
FImpl := TMyDelegate.Create(Self);
end;