Interfaces in Delphi

Introduction

Interfaces are powerful data types in the Delphi language. They provide functionality that can’t easily be provided by inheritance, like the separation of interface and implementation of a certain service. But many people still avoid their use because they either don’t know how or when to use them.

This article hopes to address most of the issues surrounding interfaces in Delphi.

Interfaces

Inheritance is a very useful mechanism to define a base class which must have certain capabilities, which can then be specified and diversified in descendant classes. Take streams, for instance. You define a basic stream class which can take data and transport (stream) them to some place, or get data from some place and present them to the user. Then you can define descendant stream classes which each stream data to or from something else, like a file, memory, another computer over a LAN, a lab machine on a USB port, a camera on FireWire, etc.

Limits of inheritance

Say we have a list class that can store data and also knows how to save these data to a stream:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
procedure TDataList.WriteDataToStream(S: TStream);
var
  I: Integer;
begin
  S.Write(ID, SizeOf(ID));
  S.Write(Count, SizeOf(Count));
  S.Write(Capacity, SizeOf(Capacity));
  for I := 0 to Count - 1 do
    S.Write(Data[I], SizeOf(Data[I]);
end;

That is fine and well when the data are a number of, say, Integers, but what if this is a list that can contain any kind of unrelated objects? You would have to tell the objects to stream themselves. You could use inheritance again, and require that all objects in the array must be descendants of a certain base class, let’s call it TStreamable, and that that base class must know how to stream an instance to a stream.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
procedure TDataObjectList.WriteDataToStream(S: TStream);
var
  I: Integer;
begin
  S.Write(ID, SizeOf(ID));
  S.Write(Count, SizeOf(Count));
  S.Write(Capacity, SizeOf(Capacity));
  for I := 0 to Count - 1 do
    Items[I].WriteToStream(S);
end;

TStreamable would have two abstract methods, WriteToStream() and ReadFromStream(), and descendants would override these methods in order to do the streaming.

But that would be a little unflexible, since now all classes you could store in that TDataObjectList would have to be descendants of TStreamable. Any classes that are not descendants of TStreamable can not be allowed, since you would not be able to call WriteToStream or ReadFromStream on them. This would mean that e.g. all classes that descend from TGraphicShape and all classes that descend from TComponent can’t be stored in this list, unless TGraphicShape and TComponent also derived from TStreamable.

Even if, say, TGraphicShape has methods with the required signatures, they are not part of the TStreamable hierarchy, so the compiler would not know how to call them, and that is why they can not be allowed.

It would be much nicer if the only requirement for a class to stored, no matter from which base class it inherits, is that is must know how to write itself to a stream and read itself from a stream. In other words, each class should contain these two methods:

1
2
procedure WriteToStream(S: Stream);
procedure ReadFromStream(S: Stream);

and the compiler should know where to find them and how to call them.

A solution: interfaces

This is where the interface types come into play. The above functions form a certain aspect any kind of class could be made to fulfill, no matter of which hierarchy it is a part. Interfaces allow you to define a set of methods like this:

1
2
3
4
5
6
type
  // In Delphi, interface names traditionally start with I
  IStreamable = interface
    procedure WriteToStream(S: Stream);
    procedure ReadFromStream(S: Stream);
  end;

The nice thing is, that any class can implement this interface, no matter if it is part of a hierarchy like the TGraphicShape I made up above, or, say, the TComponent hierarchy.

A class simply has to declare that it implements the interface, and of course, it should implement the interface by providing these two methods:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type
  TGraphicShape = class(TInterfacedObject, IStreamable)
    ...
  public
    ...
    // implementation of IStreamable
    procedure WriteToStream(S: Stream);
    procedure ReadFromStream(S: Stream);
    ...
  end;

Now the code further above can be made to store interface references:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
procedure TDataInterfaceList.WriteDataToStream(S: TStream);
var
  I: Integer;
begin
  S.Write(ID, SizeOf(ID));
  S.Write(Count, SizeOf(Count));
  S.Write(Capacity, SizeOf(Capacity));
  for I := 0 to Count - 1 do
    Items[I].WriteToStream(S); // Items[I] is of type IStreamable.
end;

That code does raise some questions, however. They will be discussed further in this article. Here, it is merely to demonstrate that an interface is a set of methods that define a contract that specifies a “can do” relationship, or sometimes a “has a” relationship1, independent of any class hierarchies.

When to use

How to use

Implements keyword

How they work

.NET

https://softwareengineering.stackexchange.com/a/108326/270380 Orthogonality https://code.tutsplus.com/tutorials/as3-101-oop-introduction-to-interfaces—active-8199 https://npf.io/2014/05/intro-to-go-interfaces/ https://softwareengineering.stackexchange.com/a/232378/270380 https://softwareengineering.stackexchange.com/a/232364/270380 https://stackoverflow.com/a/14244705/95954 https://stackoverflow.com/a/384067/95954 very good explanation of interfaces, IMO.

Standard Disclaimer for External Links

These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval of any of the products, services or opinions of the corporation or organization or individual. I bear no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.

Disclaimer and Copyright

The coding examples presented here are for illustration purposes only. The author takes no responsibility for end-user use. All content herein is copyrighted by Rudy Velthuis, and may not be reproduced in any form without the author's permission. Source code written by Rudy Velthuis presented as download is subject to the license in the files.

Back to top