Friday, August 25, 2006

Interfaces 101 - Basics

This article discusses the basics of creating an interface, writing a class that implements the interface and some simple uses of the interface. For demonstration purposes, we'll define an interface that simply returns objects from some unknown data structure and has a boolean function to indicate if the end of the structure has been reached. This is commonly called an Iterator. In this article, we'll implement this interface with a class that takes a TList as a parameter in its constructor and iterates over the items in the list. In future articles, we'll probably use this same interface to demonstrate alternate and more advanced ways of implementing it.

Defining an interface

The first step in using interfaces is to define an interface. Typically in the interface section of a unit, it is similar to defining a class. The primary differences are that the interface keyword is used instead of the class keyword, and all that can be defined are functions, procedures and properties. No variable or constant declarations are allowed.

One thing unique about interfaces is they each should have a unique GUID. This identifier is used to find the interface when given an object or interface reference of a different type. While the GUID is optional, it is strongly recommended that all interfaces have one since its absence will make certain functions quietly fail. This can lead to a bit of head scratching. A GUID can be automatically generated in the IDE by pressing Control-Shift-G.

For example:
type
  IIterator = interface
    ['{DFA2FE47-053A-4F5C-BB30-8F2A8C6936EE}']
    function AtEnd: Boolean;
    function NextItem: TObject;
  end;

Creating an implementation

An interface by itself doesn't do anything. There needs to be a class that implements it. This is done most simply by creating a class that descends from TInterfacedObject and is flagged as implementing the interface. There are additional ways of doing this that will be discussed in another article, but for now we'll use this simple way.
type
  TListIterator = class(TInterfacedObject, IIterator)
  private
    fList: TList;
    fPosition: Integer;

  public
    constructor Create(const aList: TList);
    function AtEnd: Boolean;
    function NextItem: TObject;
  end;

constructor TListIterator.Create(const aList: TList);
begin
  inherited Create;
  fList := aList;
  fPosition := 0;
end;

function TListIterator.AtEnd: Boolean;
begin
  result := fPosition >= fList.Count;
end;

function TListIterator.NextItem: TObject;
begin
  result := nil;
  if fPosition < fList.Count then
  begin
   result := fList[aPosition];
    Inc(fPosition);
  end;
end;
The first line of the declaration says we're creating a class of type TListIterator that descends from TInterfacedObject and implements IIterator. It then says we've got two private variables: one will be used to store the current position and the other will store the list object we're iterating over. We have three methods: a constructor that takes the list as a parameter and initializes the two private variables, and implementations of the AtEnd and NextItem functions declared in the interface. All methods declared in an interface need to be implemented somewhere in the class hierarchy at or above the implementing class. Typically, they are implemented in the same class as the one implementing the interface, but they could be implemented in ancestor classes.

Using the implementation

Here are some methods from a test form. First a method to create a list for testing.
procedure TfrmInterfaces101.AfterConstruction;
var
  i: Integer;
begin
  inherited;
  cList := TObjectList.Create;
  cList.OwnsObjects := True;
  for i := 1 to 10 do
    cList.Add(TObject.Create);
end;
Now simply doing some clean-up housekeepping.
procedure TfrmInterfaces101.BeforeDestruction;
begin
  inherited;
  cList.Free;
end;
Now the use of the iterator.
procedure TfrmInterfaces101.btnTestClick(Sender: TObject);
var
  lIterator: IIterator;
begin
  lIterator := TListIterator.Create(cList);
  while not lIterator.AtEnd do
    memoResults.Lines.Add(Format('%p', [Pointer(lIterator.NextItem)]));
end;
Just as if normal class types are used, this method first declares a variable of the IIterator type. Next the variable is assigned a new instance of a class. This is where the first difference can be noticed; the object is instantiated with the TListIterator class but it's assigned to a variable of a different type. Objects that implement an interface, either directly or in an ancestor, can be directly assigned to variables of that interface type. Next there's a loop that uses the two methods of the interface to get each item in the list.

Finally, notice there's no freeing of the iterator. In general, when objects are assigned to an interface there's no need to free the implementing object. The object is freed when the last interface reference goes out of scope. So, in this case, the TListIterator is freed in the compiler generated code related to the "end" statement.
Post a Comment