Delphi supports generic interfaces; for example we can declare a generic interface
type
IChecker<T> = interface
function Check(const Instance: T): Boolean;
end;
and use this generic interface as follows:
unit UseDemo;
interface
uses GenChecks;
type
TDemo<T> = class
private
FChecker: IChecker<T>;
public
constructor Create(AChecker: IChecker<T>);
procedure Check(AValue: T);
end;
implementation
{ TDemo<T> }
procedure TDemo<T>.Check(AValue: T);
begin
if FChecker.Check(AValue)
then Writeln('Passed')
else Writeln('Stopped')
end;
constructor TDemo<T>.Create(AChecker: IChecker<T>);
begin
FChecker:= AChecker;
end;
end.
To implement the above generic interface IChecker we need a generic class; the straightforward solution is
type
TChecker<T> = class(TInterfacedObject, IChecker<T>)
function Check(const Instance: T): Boolean;
end;
If the IChecker interface can be implemented like that, we need nothing else. The problem with the above implementation is that we are limited to the generic constraints on the type T and can’t use properties of specific types like Integer or string that will finally be substituted for the type T.
A more elastic solution is to introduce an abstract base type and derive the specific implementations from it. Here is a full code example:
program GenericEx1;
{$APPTYPE CONSOLE}
uses
SysUtils,
GenChecks in 'GenChecks.pas',
UseDemo in 'UseDemo.pas';
procedure TestInt;
var
Demo: TDemo<Integer>;
begin
Demo:= TDemo<Integer>.Create(TIntChecker.Create(42));
Demo.Check(0);
Demo.Check(42);
end;
procedure TestStr;
var
Demo: TDemo<string>;
begin
Demo:= TDemo<string>.Create(TStrChecker.Create('trololo'));
Demo.Check('ololo');
Demo.Check('olololo');
end;
begin
TestInt;
TestStr;
ReadLn;
end.
unit GenChecks;
interface
type
IChecker<T> = interface
function Check(const Instance: T): Boolean;
end;
type
TCustomChecker<T> = class(TInterfacedObject, IChecker<T>)
protected
FCheckValue: T;
function Check(const Instance: T): Boolean; virtual; abstract;
public
constructor Create(ACheckValue: T);
end;
TIntChecker = class(TCustomChecker<Integer>)
protected
function Check(const Instance: Integer): Boolean; override;
end;
TStrChecker = class(TCustomChecker<string>)
protected
function Check(const Instance: string): Boolean; override;
end;
implementation
{ TCustomChecker<T> }
constructor TCustomChecker<T>.Create(ACheckValue: T);
begin
FCheckValue:= ACheckValue;
end;
{ TIntChecker }
function TIntChecker.Check(const Instance: Integer): Boolean;
begin
Result:= Instance = FCheckValue;
end;
{ TStrChecker }
function TStrChecker.Check(const Instance: string): Boolean;
begin
Result:= Length(Instance) = Length(FCheckValue);
end;
end.
In the above example each interface reference ICheck references its own class instance; this is necessary because every instance contains a parameter (FCheckValue) set in the constructor. If an implementation does not require such a parameter creating new instances for every interface reference will be an overhead. A better solution is to use a singleton instance.
Here is a full code example for the integer type:
program GenericEx2;
{$APPTYPE CONSOLE}
uses
SysUtils,
GenChecks in 'GenChecks.pas',
UseDemo in 'UseDemo.pas';
procedure TestInt;
var
Demo: TDemo<Integer>;
begin
Demo:= TDemo<Integer>.Create(TIntChecker.Ordinal);
Demo.Check(0);
Demo.Check(42);
end;
begin
TestInt;
ReadLn;
end.
unit GenChecks;
interface
uses Generics.Defaults;
type
IChecker<T> = interface
function Check(const Instance: T): Boolean;
end;
TCustomChecker<T> = class(TSingletonImplementation, IChecker<T>)
protected
function Check(const Instance: T): Boolean; virtual; abstract;
end;
TIntChecker = class(TCustomChecker<Integer>)
private
class var
FOrdinal: TCustomChecker<Integer>;
public
class function Ordinal: TIntChecker;
end;
implementation
type
TOrdinalIntChecker = class(TIntChecker)
public
function Check(const Instance: Integer): Boolean; override;
end;
{ TOrdinalIntChecker }
function TOrdinalIntChecker.Check(const Instance: Integer): Boolean;
begin
Result:= Instance = 42;
end;
{ TIntChecker }
class function TIntChecker.Ordinal: TIntChecker;
begin
if FOrdinal = nil then
FOrdinal := TOrdinalIntChecker.Create;
Result := TIntChecker(FOrdinal);
end;
end.