Delphi lets a class satisfy an interface without writing the interface's methods itself.
Put the implements keyword on a property, and the interface is served
by that property's value instead. It is composition instead of inheritance: the real
logic lives in a separate object the class merely holds a reference to.
type
{ The contract }
IWorker = interface
['{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}']
procedure Do_Work;
end;
{ The class that actually does the work }
TWorker_Impl = class (TInterfacedObject, IWorker)
public
procedure Do_Work;
end;
{ The host - claims IWorker, delegates to the Worker property }
TCompany = class (TInterfacedObject, IWorker)
private
FWorker : IWorker;
public
property Worker : IWorker read FWorker write FWorker implements IWorker;
end;
procedure TWorker_Impl.Do_Work;
begin
Writeln ('The worker is doing the actual labor!');
end;
Using it:
var
LCompany : TCompany;
LWorker : IWorker;
begin
LCompany := TCompany.Create;
LCompany.Worker := TWorker_Impl.Create;
{ Do_Work is not a method of TCompany - it is reached through the IWorker
that TCompany hands out on behalf of its Worker property }
if LCompany.GetInterface (IWorker, LWorker)
then LWorker.Do_Work;
LWorker := nil;
LCompany.Free;
end;
Adding implements IWorker to the property tells the compiler that
TCompany does not implement IWorker's methods itself. The
IWorker entry in TCompany's interface table points at the
property instead. When IWorker is requested from TCompany, the
property is read and the interface it holds - the worker's own IWorker - is
handed back. From then on calls go straight to the worker; TCompany is not
involved. The effect is the same as a hand-written pass-through:
procedure TCompany.Do_Work;
begin
FWorker.Do_Work;
end;
with one important difference: the IWorker you receive belongs to the
worker, not to TCompany - see Identity below.
The property must be of the interface type being delegated, or of a class type that implements that interface's methods. Both of these are valid:
property Worker : IWorker read FWorker write FWorker implements IWorker;
property Worker : TWorker_Impl read FWorker_Impl write FWorker_Impl implements IWorker;
With an interface-typed property, Delphi's automatic reference counting applies
to the delegate. Assigning to LCompany.Worker increments the new value's
reference count and releases whatever the property held before.
A class-typed property gets no reference counting - the field is a plain object
reference. If that class descends from TInterfacedObject, its count is
driven only by the interface references handed out through the host; when the last
one is released the delegate destroys itself while the host still holds the field,
leaving a dangling reference. A class-type delegate should not count its own
references: descend it from TAggregatedObject or
TContainedObject, and have the host create and free it.
Because the IWorker handed out by an interface-typed property is the
worker's own, its QueryInterface, _AddRef and
_Release are the worker's too. Asking that IWorker for any
other interface TCompany supports fails, and the reference counting lands
on the worker, not on TCompany. When the delegate must behave as part of
the host, use a class-typed property and descend the delegate from
TAggregatedObject, which forwards QueryInterface and
reference counting to the host, or TContainedObject, which forwards
reference counting only.
Mixing host and delegate methods works only with a class-typed property. The delegate class and its ancestors are searched first; any method it does not provide is then taken from the host. A method on the host fills a gap - it does not take precedence over one the delegate already provides.
With an interface-typed property, as in the example above, the whole interface
comes from the property; a Do_Work declared on TCompany is
not used for calls made through IWorker. To run code before or after the
worker, drop implements and write the pass-through method by hand.
implements is a property specifier - it goes only on a property
declaration (after the read specifier; a read specifier is
required). It cannot be attached directly to a field declaration.
THuman_Worker
for a TRobot_Worker) and callers that only see the host as
IWorker never need to changeThe two lines (with LWorker declared as IWorker)
LWorker := TWorker_Impl.Create;
LCompany.Worker := LWorker;
can be written as one:
LCompany.Worker := TWorker_Impl.Create;
TWorker_Impl.Create returns a class instance; because
TWorker_Impl implements IWorker, the compiler implicitly
converts it to an interface reference for the assignment.
Because TWorker_Impl descends from TInterfacedObject, it is
reference counted. The moment it's assigned to LCompany.Worker its count
becomes 1; TCompany effectively owns it. Assign a different worker (or
nil) later, or free TCompany itself, and the count drops to
zero - the worker is destroyed automatically. Don't hold it in a separate class-typed
variable and don't call .Free on it yourself.
{ No try/finally needed for the worker - it cleans itself up when
LCompany is freed or LCompany.Worker is reassigned }
LCompany.Worker := TWorker_Impl.Create;
Critical warning: this only works safely if the implementation class is
reference counted - descended from TInterfacedObject, or implementing
counting in its own _AddRef and _Release. A class declared
directly against TObject won't compile, as it lacks
QueryInterface, _AddRef and _Release; one that
implements them without counting (TNoRefCountObject, for example) compiles,
but nothing ever frees it - the object leaks.
LCompany in the examples above is held in a plain class-typed variable,
not an interface variable, so it still needs an explicit LCompany.Free;
only the delegate it holds is reference counted. That is safe here because every
IWorker taken from TCompany is the worker's own, so
TCompany's count is never touched. If anything obtains an interface on
TCompany itself, such as an IInterface reference, its count
rises to 1 and falls back to 0, TCompany destroys itself, and the later
LCompany.Free frees it a second time.
Please donate if helpful