top header
interfacesterm

 

home contents introduction com as a better C++ interfaces classes apartments labs lab setup quizzes
 
previous
Interface Preamble
 
What is an interface in general terms?

An interface defines a protocol that allows unrelated entities to interact. A protocol is simply an agreed-upon behavior. It can be thought of as a well-defined abstraction or encapsulation layer between some client code and some server code. This abstract behavior takes the form of a collection of logically related requests that a client can make of a server component.

You have seen many interfaces in your lifetime. Some interfaces are not object oriented, such as the Windows API or the sockets API. Other interfaces are object-oriented, such as MFC or STL.

What is the point of any interface?

The basic reason for an interface is to separate interface from implementation. A client can program in the abstract and doesn't really know or care where the implementation of the interface comes from. As long as an interface is immutable then a change of server or changes within a server implementation do not affect the client. Likewise a server component doesn't really know or care which client is using it. This gives server components a great deal of flexibility in terms of their exact implementation. For instance, different servers could implement the same interface in different languages or using different implementation techniques.

What is an interface in OO terms?

The standard technique for non object-oriented interface-based programming is to use handles. A handle is an opaque cookie given to the client by the server that identifies some state. The cookie is given back to the server as a parameter to one of the server-defined APIs. Such APIs define behavior and are used to access and change the state defined by the cookie in a controlled way. As a good example of this type of interface-based programming look at the Windows programming API.

In object oriented programming we are used to dealing with classes and objects and not handles and 'flat' API sets. In OO terms, an interface defines a named collection of methods but does not implement them. A class that implements the interface agrees to implement all of the methods defined in the interface, thereby agreeing to certain behavior. An interface can be thought of as an abstract type. A class that implements the interface becomes type compatible with it. An interface is sometimes referred to also as a protocol class.

C++ has no language support for interfaces but with a bit of care and attention to detail, interface-based programming can be done using pure abstract base classes.

Java, for instance, does have language support for interfaces.

An example of an OO interface

Let us look at a C++ interface that defines the behavior of serializing state data held by a class to a named entity.

struct ISerialize
{
    virtual BOOL Save(const char *name)=0;
    virtual BOOL Load(const char *name)=0;
};

Now two totally different classes, unrelated in terms of their position in the class hierarchy, can choose to implement the methods of this interface.

class CDog : public CAnimal, public ISerialize
{
    void Woof();
    ...
    BOOL Save(const char *name);
    BOOL Load(const char *name);
};

class CButton : public CWindow, public ISerialize
{
    int GetCaptionText(char *text, int *plen);
     ...
    BOOL Save(const char *name);
    BOOL Load(const char *name);
};

Both classes can be manipulated through their ISerialize interface as follows:

CDog *pd = new CDog("Fido");
CButton b("Press me", 100, 50);

ISerialize *pisd = dynamic_cast<ISerialize *>(pd);
if (NULL!=pisd)
   pisd->Save("HKEY_CLASSES_ROOT\Dog\Name");

ISerialize *pisb = dynamic_cast<ISerialize *>(&b);
if (NULL!=pisb)
    pisb->Save("Button.txt");

In Java the code might look like this.

interface ISerialize
{
    boolean Save(String name);
    boolean Load(String name);
};

class JDog extends JAnimal implements ISerialize
{
    void Woof();
    ...
    boolean Save(String name);
    boolean Load(String name);
};

class JButton extends JWindow implements ISerialize
{
    String GetCaptionText();
     ...
    boolean Save(String name);
    boolean Load(String name);
};

JDog d = new JDog("Fido");
JButton b = new JButton("Press me", 100, 50);

try {
    ISerialize isd = (ISerialize)d;
    isd.Save("AnimalApplication.Dog.Name");
} catch(ClassCastException)
{
    ...
};
try {
    ISerialize isb = (ISerialize)b;
    isb.Save("Button.txt");
} catch(ClassCastException)
{
    ...
};

The ISerialize interface has tightly-defined syntax. Every detail of that syntax must be satisfied and made concrete by the implementing class. However, the semantics of the interface are loose enough that one implementation class, CDog, can save its name to a registry key and another, CButton, can save its caption text to a file. There is room for interpretation of the exact details of how the interface is implemented. This is good since the client of the interface can deal with it in a very abstract way regardless of the exact implementation. Notice again that classes at totally different places in the class inheritance hierarchy can be type compatible with each other.

We could, of course, take this approach to its logical conclusion and define interfaces and classes thus.

struct IAnimal
{
    virtual int GetAge()=0;
    virtual void SetAge(int age)=0;
    ...
};
struct IDog : public IAnimal
{
    virtual void Woof()=0;
    ...
};
struct IWindow
{
    virtual void Move(int x, inty, int width, int height)=0;
    virtual void SetSize(int width, int height)=0;
    virtual void GetSize(int *pwidth, int *pheight)=0;
    ...
};
struct IButton : public IWindow
{
    virtual int GetCaptionText(char *text, int *plen)=0;
    ...
};
struct IFoo
{
    virtual int SomeFunction()=0;
    ...
};
class CDog : public IDog, public ISerialize
{
    void Woof();
    ...
    BOOL Save(const char *name);
    BOOL Load(const char *name);
};

class CButton : public IButton, public ISerialize, public IFoo
{
    virtual int SomeFunction();
    ...
    int GetCaptionText(char *text, int *plen);
     ...
    BOOL Save(const char *name);
    BOOL Load(const char *name);
};

What makes any interface really useful?

To be useful an interface should have the following characteristics.

  • The interface is uniquely identified, i.e. client and server can agree on the interface.
  • The interface never changes over its lifetime.
  • The interface doesn't betray any implementation details to the client. It defines behavior only.

The most useful interfaces are those that are defined tightly enough to allow the expression of the desired behavior but defined loosely enough to allow for different polymorphic implementations.

Interface definition must not betray any implementation details. The interface mechanism must be as simple as possible. It must minimize the point of contact between a client and a server. There must be some simple rules covering how the interface is used by the client and implemented by the server. If this is the case then it maximizes the chances of being able to define a uniform binary representation for the interface across all languages and environments on a given platform. It might even be possible to define a uniform network representation for the interface across all platforms. In this case an interface can be thought of as a boundary crossing between the client and the server. It might cross language boundaries, module boundaries and machine boundaries.

So where are we?
  • Interface-based programming lends itself well to building plug-compatible systems of objects. Interfaces can be thought of as abstract wrappers for user-specified data types that have well-defined behavior but no implementation.
  • In practical C++ terms, an interface is an abstract base class that contains only pure virtual functions, i.e. with no implementation at all. It therefore represents a skeleton for what a data type would look like and how it would be accessed. Each function in the interface represents an individual request and the parameters of that function represent the data to exchange.
  • Interfaces are defined and uniquely named once, at design-time, to determine a protocol of expected behavior, or a contract, between a component and a component user. Typically a component will expose several interfaces that represent many facets of its behavior. Typically an interface is implemented many times by many different components, i.e. it is a reusable contract.
  • An interface must be uniquely identifiable and immutable within the scope of its use, otherwise it is not much of a contract. We will see next that because COM represents a bridge between many different languages, COM interfaces must be uniquely identifiable and immutable within the known universe.
  • An interface defines loose semantics. The precise behavior of an interface is tied-down when a component provides a concrete implementation of the interface with code for all of the functions contained in the interface. This means that the interface and the implementation of that interface are separate. The single point of contact between a component and its consumer is as minimal as it can be - the interface.
  • The interface hierarchy is independent of the class hierarchy. Component users deal with interfaces and class relationships are not forced on interface users. Interface users can be largely ignorant of the classes they are using.
  • Because interfaces are abstract, i.e. they define behavior and do not contain any implementation code, they do not suffer from the vagaries of different languages/environments. It is theoretically possible to define a binary standard for accessing an interface allowing components and component users to be implemented in many different languages, in many different places, using many different tools, i.e. it becomes a binary contract.
  • As long as there is a mechanism to discover interfaces at run-time, we can define new interfaces to wrap up new data types with new behavior and component functionality can evolve safely over time without breaking old component users.
  • They are pretty much the only sane way to dynamically link method code at run-time.

So, let's go back and look at COM interfaces.


Top of document