Thursday, August 3, 2006

What are interfaces?

Interfaces are a means of describing a unit of functionality without regard to how it is implemented. They provide a means of decoupling what an object does from how it does it. In some ways, they are similar to a pure abstract class definition.

A great example of a good use of interfaces is the Iterator interface in .NET. It is small and does one thing well: it provides the concept of a list of items without giving any detail about how that list is stored. Various types of container classes implement the Iterator interface, in different ways which are specific to how each container stores its data. Users of the Iterator interface can iterate through a collection of items without knowing if the collection is a linked list, a binary tree or coming from a remote machine through a TCP/IP socket (assuming there's some object which communicates through a socket and implements the Iterator interface).

In Delphi, they can also do two helpful things.

First, since an object can implement multiple interfaces, they can provide some of the useful features provided by multiple inheritance. For example, an object can implement an IStreamable interface, indicating that it knows how to stream itself to some streaming mechanism. It can also implement an IPerson interface indicating that it has Name, Home address and Birthdate properties. Interfaces implementations can also be delegated to properties. This allows a multiple classes with different object hierarchies to implement an interface but keep the implementation in a single helper class.

Second, they can provide a means of automatic lifetime management of objects. There's always the ongoing question of who's responsible for freeing created objects. Some environments, such as Java and .NET, use garbage collection which use various means of determining when an object is no longer used and getting rid of it. Historically, Delphi has said it's the programmers responsibility to call .Free when they are done with the object. Based on this, one best practice says the object which creates another object is responsible for freeing. Another is the owner pattern, the most familiar example is TComponent, where an owner is assigned who's responsible for freeing the object.

Reference counted interfaces are another means. Any object which descends from TInterfacedObject, or which implements IUnknown and incorporates reference counting, can use this method. Basically, it leaves the compiler and run-time system responsible for keeping track of how many references there are to an interface and destroying the implementing object when the last reference goes away.

This can be very handy in many situations. One example is the Factory pattern where the whole purpose is to decouple the creation of an object from the object using the created object. Another example is in threads, where objects may be passed between threads with no clear concept of owner. And finally, in lifetime management of normal objects, it can eliminate just busy-work housecleaning.

No comments: