Interfaces & Reference Counting
An interface declares methods (and properties) without implementation.
28.1 Declaring and implementing interfaces
An interface declares methods (and properties) without implementation. A class can implement many interfaces while inheriting from one class.
program InterfacesDemo;
{$mode objfpc}{$H+}
uses SysUtils;
type
ILogger = interface
['{6E3A2C1B-8D4F-4B6A-9C2E-1F0A7B5D3E91}'] // GUID enables "as", Supports, QueryInterface
procedure Log(const Msg: string);
end;
IFormatter = interface
['{0B9D6A41-2E7C-4F3B-A8D5-6C1E9F2A7B40}']
function Format(const Msg: string): string;
end;
TConsoleLogger = class(TInterfacedObject, ILogger, IFormatter)
public
destructor Destroy; override;
procedure Log(const Msg: string);
function Format(const Msg: string): string;
end;
destructor TConsoleLogger.Destroy;
begin
Writeln('Logger destroyed');
inherited Destroy;
end;
procedure TConsoleLogger.Log(const Msg: string);
begin
Writeln(Format(Msg));
end;
function TConsoleLogger.Format(const Msg: string): string;
begin
Result := '[' + TimeToStr(Now) + '] ' + Msg;
end;
procedure Run;
var
Logger: ILogger;
Fmt: IFormatter;
begin
Logger := TConsoleLogger.Create; // ref count = 1
Logger.Log('Started');
if Supports(Logger, IFormatter, Fmt) then // ref count = 2
Writeln(Fmt.Format('via IFormatter'));
end; // both references released -> object destroyed
…28.2 Reference counting rules
TInterfacedObject implements _AddRef/_Release; the object frees itself when the count reaches zero.
Interface variables are managed: initialized to nil, released when they go out of scope or are reassigned.
Do not mix object references and interface references to the same reference-counted object: the object may be freed while you still hold the object reference, or you may Free it while interfaces still point to it.
Pass interfaces as const parameters to avoid unnecessary reference counting — but never pass a freshly created object directly to a const interface parameter (DoWork(TFoo.Create)), because no reference is ever taken and the object can leak.
Reference cycles (A holds B, B holds A) leak. Break them with [weak] or [unsafe] references (Delphi 10.1+), raw Pointer references, or by clearing references explicitly. FPC does not support [weak].
28.3 Interface features
IShape = interface(IInterface); IFilledShape = interface(IShape)
property Name: string read GetName; (getter must be a method)
Intf as ILogger (raises if unsupported), Supports(Obj, ILogger, L)
property Logger: ILogger read FLogger implements ILogger; procedure ILogger.Log = WriteLogLine; maps an interface method to a differently named method Non-refcounted (FPC)
{$interfaces corba} — no GUID required, no reference counting, no IInterface base Non-refcounted (Delphi) descend from TNoRefCountObject (recent Delphi; formerly TSingletonImplementation) or implement _AddRef/_Release returning -1
{$interfaces com} (FPC default), Windows COM objects, safecall
28.4 Programming to interfaces
Swapping TFileStorage for TMemoryStorage in unit tests requires no change in TOrderService — dependency injection the Pascal way. Frameworks: Spring4D (Delphi DI container and collections).
type
IStorage = interface
['{A1C4E2F0-5B3D-4E7A-8F19-2D6C0B4A9E37}']
procedure Save(const Key, Value: string);
function Load(const Key: string): string;
end;
TOrderService = class
private
FStorage: IStorage; // depends on the abstraction, not on a concrete class
public
constructor Create(AStorage: IStorage);
procedure PlaceOrder(const Id: string);
end;