Declaring an interface
In Delphi, we can declare an interface like so:
type
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
/// add two signed 32 bit integers
function Add(n1,n2: integer): integer;
end;
It just sounds like a class definition, but, as you can see:
- It is named
ICalculator, and notTCalculator: it is a common convention to start an interface name with aI, to make a difference with aTfor a class or other implementation-level type definition; - There is no visibility attribute (no
private / protected / public / publishedkeywords): in fact, it is just as if all methods were published; - There is no fields, just methods (fields are part of the implementation,
not of the interface): in fact, you can have properties in your interface
definition, but those properties shall redirect to existing getter and setter
methods, via
readandwritekeywords; - There is a strange number below the interface name, called a
GUID: this is an unique identifier of the interface - you can create such a genuine constant on the editor cursor position by pressingCtrl + Shift + Gin the Delphi IDE; - But the methods are just defined as usual.
Implementing an interface with a class
Now that we have an interface, we need to create an implementation.
Our interface is very basic, so we may implement it like this:
type TServiceCalculator = class(TInterfacedObject, ICalculator) protected fBulk: string; public function Add(n1,n2: integer): integer; procedure SetBulk(const aValue: string); end; function TServiceCalculator.Add(n1, n2: integer): integer; begin result := n1+n2; end; procedure TServiceCalculator.SetBulk(const aValue: string); begin fBulk := aValue; end;You can note the following:
- We added
ICalculatorname to theclass()definition: this class inherits fromTInterfacedObject, and implements theICalculatorinterface; - Here we have
protectedandpublickeywords - but theAddmethod can have any visibility, from the interface point of view: it will be used as implementation of an interface, even if the method is declared asprivatein the implementation class; - There is a
SetBulkmethod which is not part of theICalculatordefinition - so we can add other methods to the implementation class, and we can even implement several interfaces within the same method (just add other interface names after likeclass(TInterfacedObject, ICalculator, IAnotherInterface); - There a
fBulkprotected field member within this class definition, which is not used either, but could be used for the class implementation. - Here we have to code an implementation for the
TServiceCalculator.Add()method (otherwise the compiler will complain for a missing method), whereas there is no implementation expected for theICalculator.Addmethod - it is perfectly "abstract".
Using an interface
Now we have two ways of using our TServiceCalculator
class:
- The classic way;
- The abstract way (using an interface).
The "classic" way, using an explicit class instance:
function MyAdd(a,b: integer): integer;
var Calculator: TServiceCalculator;
begin
Calculator := TServiceCalculator.Create;
try
result := Calculator.Add(a,b);
finally
Calculator.Free;
end;
end;
Note that we used a try..finally block to protect the instance
memory resource.
Then we can use an interface:
function MyAdd(a,b: integer): integer; var Calculator: ICalculator; begin ICalculator := TServiceCalculator.Create; result := Calculator.Add(a,b); end;
What's up over there?
- We defined the local variable as
ICalculator: so it will be aninterface, not a regular class instance; - We assigned a
TServiceCalculatorinstance to thisinterfacevariable: the variable will now handle the instance life time; - We called the method just as usual - in fact, the computation is performed
with the same exact expression:
result := Calculator.Add(a,b); - We do not need any
try...finallyblock here: in Delphi, interface variables are reference-counted: that is, the use of the interface is tracked by the compiler and the implementing instance, once created, is automatically freed when the compiler realizes that the number of references to a given interface variable is zero; - And the performance cost is negligible: this is more or less the same as calling a virtual method (just one more redirection level).
In fact, the compiler creates an hidden try...finally block in
the MyAdd function, and the instance will be released as soon as
the Calculator variable is out of scope. The generated code could
look like this:
function MyAdd(a,b: integer): integer;
var Calculator: TServiceCalculator;
begin
Calculator := TServiceCalculator.Create;
try
Calculator.FRefCount := 1;
result := Calculator.Add(a,b);
finally
dec(Calculator.FRefCount);
if Calculator.FRefCount=0 then
Calculator.Free;
end;
end;
Of course, this is a bit more optimized than this (and thread-safe), but you have got the idea.
There is more than one way to do it
One benefit of interfaces we have already told about, is that it is "orthogonal" to the implementation.
In fact, we can create another implementation class, and use the same interface:
type TOtherServiceCalculator = class(TInterfacedObject, ICalculator) protected function Add(n1,n2: integer): integer; end; function TOtherServiceCalculator.Add(n1, n2: integer): integer; begin result := n2+n1; end;
Here the computation is not the same: we use n2+n1 instead of
n1+n2... of course, this will result into the same value, but we
can use this another method for our very same interface, by using its
TOtherServiceCalculator class name:
function MyOtherAdd(a,b: integer): integer;
var Calculator: ICalculator;
begin
ICalculator := TOtherServiceCalculator.Create;
result := Calculator.Add(a,b);
end;
Here comes the magic
Now you may begin to see the point of using interfaces in a client-server framework like ours.
Our mORMot is able to use the same interface definition on both
client and server side, calling all expected methods on both sides, but having
all the implementation logic on the server side. The client application will
transmit method calls (using JSON) to the server (using a "fake" implementation
class created on the fly by the framework), then the execution will take place
on the server (with obvious benefits), and the result will be sent back to the
client, as JSON. The same interface can be used on the server side, and in this
case, execution will be in-place, so very fast.
By creating a whole bunch of interfaces for implementing the business logic of your project, you will benefit of an open and powerful implementation pattern. Working from Delphi 6 up to XE2.
More on this later on... see this blog article and its follow-ups, including this Interface based Services in mORMot - Overview document.
Do not forget to take a new look at good principles of playing with interfaces: the SOLID design principles.
And comments are welcome on our forum, just as usual!
