When you define an abstract class to represent the base class of a hierarchy, you can come to a point at which the abstract class is so abstract that it only lists a series of virtual functions without providing any implementation. This kind of purely abstract class can also be defined using a specific technique: an interface. For this reason, we refer to these classes as interfaces .
Technically, an interface is not a class, although it may resemble one. Interfaces are not classes, because they are
Interface type objects are reference-counted and automatically
A class can inherit from a single base class, but it can implement multiple interfaces.
Just as all classes descend from TObject , all interfaces descend from IInterface , forming a totally separate hierarchy.
| Note |
The base interface class was
IUnknown
until Delphi 5, but Delphi 6 introduced a new
|
It is important to note that interfaces support a slightly different OOP model than classes. Interfaces provide a less restricted implementation of polymorphism. Object reference polymorphism is based around a specific branch of a hierarchy. Interface polymorphism works across an entire hierarchy. Certainly, interfaces favor encapsulation and provide a looser connection between classes than inheritance. Notice that the most recent OOP languages, from Java to C#, have the notion of interfaces.
Here is the syntax of the declaration of an interface (which, by convention, starts with the letter I ):
type
ICanFly =
interface
[
'{EAD9C4B4-E1C5-4CF4-9FA0-3B812C880A21}'
]
function
Fly: string;
end;
This interface has a GUID (Globally Unique Identifier)—a numeric ID following its declaration and based on Windows conventions. You can generate these identifiers by pressing Ctrl+Shift+G in the Delphi editor.
| Note |
Although you can compile and use an interface without specifying a GUID for it, you'll
|
Once you've declared an interface, you can define a class to implement it:
type TAirplane = class (TInterfacedObject, ICanFly) function Fly: string; end;
The RTL already provides a few base classes to implement the basic behavior required by the IInterface interface. For internal objects, use the TInterfacedObject class I've used in this code.
You can implement interface methods with static methods (as in the previous code) or with virtual methods. You can override virtual
| Note |
The compiler has to generate stub routines to fix up the interface call entry points to the matching method of the implementing class, and adjust the
self
pointer. The interface method stubs for static methods have to adjust
self
and jump to the real method in the class. The interface method stubs for virtual methods are much more complicated, requiring about four times more code (20 to 30 bytes) in each stub than the static case. Also, adding more virtual methods to the implementing class just bloats the virtual method table (VMT) that much more in the implementing class and all its descendents. An interface already has its own VMT, and redeclaring an interface in descendents to rebind the interface to new methods in the
|
Now that you have defined an implementation of the interface, you can write some code to use an object of this class, through an interface-type variable:
var Flyer1: ICanFly; begin Flyer1 := TAirplane.Create; Flyer1.Fly; end;
As soon as you assign an object to an interface-type variable, Delphi automatically checks to see whether the object implements that interface, using the
as
operator. You can explicitly express this operation as
Flyer1 := TAirplane.Create as ICanFly;
| Note |
The compiler generates different code for the as operator when used with interfaces or with classes. With classes, the compiler introduces run-time checks to verify that the object is effectively "type-compatible" with the given class. With interfaces, the compiler sees at compile time that it can extract the necessary interface from the available class type, so it does. This operation is like a "compile-time as ," not something that exists at run time. |
Whether you use the direct assignment or the as statement, Delphi does one extra thing: It calls the _AddRef method of the object (defined by IInterface ). The standard implementation of this method, like the one provided by TInterfacedObject , is to increase the object's reference count. At the same time, as soon as the Flyer1 variable goes out of scope, Delphi calls the _ Release method (again part of IInterface ). The TInterfacedObject 's implementation of _Release decreases the reference count, checks whether the reference count is zero, and, if necessary, destroys the object. For this reason, the previous example doesn't include any code to free the object you've created.
In other words, in Delphi, objects referenced by interface
| Warning |
When using interface-based objects, you should generally access them only with object references or only with interface references. Mixing the two approaches breaks the reference counting scheme provided by Delphi and can cause memory errors that are extremely difficult to track. In practice, if you've decided to use interfaces, you should probably use exclusively interface-based variables. If you want to be able to mix them, instead, disable the reference counting by writing your own base class instead of using TInterfacedObject . |