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 aT
for a class or other implementation-level type definition; - There is no visibility attribute (no
private / protected / public / published
keywords): 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
read
andwrite
keywords; - 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 + G
in 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
ICalculator
name to theclass()
definition: this class inherits fromTInterfacedObject
, and implements theICalculator
interface; - Here we have
protected
andpublic
keywords - but theAdd
method 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 asprivate
in the implementation class; - There is a
SetBulk
method which is not part of theICalculator
definition - 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
fBulk
protected 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.Add
method - 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
TServiceCalculator
instance to thisinterface
variable: 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...finally
block 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!