| 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.
- 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.

|