Patterns of Interface-Based Programming
Friedrich Steimann, Universität
Hannover, Germany
Philip Mayer, Universität Hannover, Germany
|
 |
REFEREED
ARTICLE

PDF Version |
Abstract
Modern software architectures heavily promote the use of interfaces.
Originally conceived as a means to separate specification from implementation,
popular programming languages toady accommodate interfaces as special
kinds of types that can be used – in place of classes – in
variable declarations. While it is clear that these interfaces offer
polymorphism independent of the inheritance hierarchy, little has been
said about the systematic use of interfaces, or how they are actually
used in practice. By providing a set of basic patterns of interface
use together with numbers of their frequency we provide insights that
should be of interest not only to the practising programmer, but also
to the designers and analysts of large code bases.
1 INTRODUCTION
After the hype has gone, what remains from a new technology is often
not what it has originally been acclaimed for. In the case of object-oriented
programming, it seems that the praise for its potential of code reuse
through implementation inheritance has vanished in favour of an appreciation
of what is sometimes called interface inheritance or, more generally,
interface-based programming [Pattison00]. This programming style derives
from the fact that instances of different classes are allowed to take
the same place (i.e., can be assigned to the same variable) as long
as they guarantee to conform to the same interface specification, and
that this substitutability is independent from whether or not the classes
share implementation. In interface-based programming, variables are
therefore typed with interfaces rather than classes.
The programming language Java has
recently popularized the interface-as-type concept: with it, the designers
of a program have the choice to code
abstract types (types that cannot be instantiated) as either abstract
classes or interfaces. The Java language
specification however is rather reticent about the reason to introduce
a separate interface construct;
in fact, from reading the documentation one is led to believe that
the primary purpose of its existence is to compensate for Java’s
lack of multiple inheritance.
Figure 1. Development of interface utilization in the
JDK.
Figure 1 gives an impression of how the use of interfaces has developed
over the past five major releases of the Java application
programming interface (API, called JDK hereafter). Whereas the ratio
of classes
to interfaces almost remained constant (approx. 5.5:1 across all releases;
note that equal ratios translate to equal distances on a logarithmic
scale), that of interface-typed to class-typed variables nearly doubled
with the change to Java 2 (JDK 1.2; approx. from 1:9 to 1:5). At the
same time, the average number of interfaces implemented per class jumped
from 0.8 to 1.8.
Considering that in Java classes must
explicitly declare to implement interfaces for their instances to be
assignable to correspondingly
typed variables, one might assume that the increased availability of
interface-implementing classes accounts for the increased popularity
of interface-typed (in relationship to class-typed) variables. However,
the even more dramatic increase of interface implementations one release
earlier (which was more than 2.5-fold) was not accompanied by a corresponding
increase in the ratio of interface to class-typed variables (which
instead remained almost constant); in fact, it goes back to a large
part to the introduction of the Serializable interface,
which is implemented by 43% of all classes of the JDK 1.1.6, but which
has only few (30)
variables declaring it as their type. As it turns out, one contributor
to the considerable increase in interface-typed variables from JDK
1.1 to JDK 1.2 is the introduction of the Java collections
framework, whose 11 interfaces account for 13% of all interface-typed
variables
in 1.2, but – contrary to Serializable – come
with only few implementing classes. In the same vein, but much more
than collections
the GUI framework Swing (also introduced with Java 2)
has contributed to the statistics: its interfaces come with 54% of
all interface-typed
variables of the JDK 1.2. While this may be taken as a first evidence
of the existence of different usage patterns for interfaces, we note
here that since then, both the average number of interface implementations
and the ratio of interface to class-typed variables have remained almost
constant, suggesting that the use of interfaces, as well as perception
of their utility, – at least by the authors of the JDK – has
settled.
Outside the realm of Java, one can observe that especially within
the context of large APIs and frameworks such as CORBA, J2EE, COM or
.NET
the use of interfaces is heavily promoted. Not unlike in the Java community,
however, this is usually done in an ad hoc fashion, without an underlying
theory when and how interfaces are to be used. While this question
may have remained unobvious as long as programming languages such as
C++ (which distinguish only between abstract and concrete classes,
not between abstract classes and interfaces) were being used, with
languages such as Java and C# the programmer must make an explicit
choice, and this choice should be based on considerations reproducible
by the readers of a program.
In the following, we elaborate on certain frequent patterns of interface-based
programming, trying to sharpen the reader’s awareness of the
different uses of the interface concept, and provide programmers with
robust criteria as to when one kind of interface or the other is being
used. We start in Section 2 with a short survey of the evolution of
interfaces as types (the indispensable prerequisite of interface-based
programming), and define a few basic properties of interfaces in Section
3. Based on these definitions, Section 4 then provides a classification
of interfaces together with the patterns of their use. A brief discussion
of related work and a justification of our approach conclude our work.
2 EVOLUTION OF THE INTERFACE-AS-TYPE CONCEPT
Defined as “a shared boundary across which information is passed” [IEEE91],
interface is a very general notion that has many uses in computer science.
Interfaces between software components were pioneered by Dijkstra and
Parnas in their work on software architecture and good design – the
fact that it did not take long until interfaces became distinct programming
entities in languages as respected as Modula and
Ada must be considered strong evidence for the import of the concept.
Today it appears that
the terms module and interface have almost become inseparable in software
engineering: the ACM computing classification system for instance lists
them jointly under Design Tools and Techniques (entry D.2.2).
With the advent of object-oriented programming, classes quickly became
the primary unit of modularization, replacing to a large part for the
more heterogeneous concept of a module. The interface of a class is
an abstract data type (including formal behaviour specification) for
which the class provides one (out of many possible) implementations.
However, in most contemporary object-oriented programming languages
interface specification is inseparably tied to the definition of a
class (by tagging some of the class’s features as public and
others as private) – it can neither be shared by other classes
(except of course by its subclasses), nor can the class implement more
than one abstract data type (except those inherited from its superclasses).
We call an abstract data type that is part of the class definition
the implicit interface of the class; implicit interfaces will play
a role in some of our further considerations.
Clu was perhaps the first language
to separate a class from its interface by way of associating a distinct
type with each. While the primary
reason for this was to permit separate compilation of modules (called
clusters in Clu), it would also have
made possible the selection among different implementations of an interface
at run-time, as its authors
note [Liskov77]. In fact, the full power of separating interfaces and
implementation types is unfolded only when combined with type subsumption
and the principle of substitutability: if instances of different types
realize the same interface, they are allowed to replace for each other.
Although more fundamental than the substitutability tied to the inheritance
hierarchies of object-oriented programming languages, it took some
time for so-called inclusion polymorphism to become decoupled from
subclassing: the creators of Java confess
to have borrowed the language’s
interface concept from Objective-C (called
protocols there), but the true origin is difficult to trace1 .
C# has taken over the interface-as-type concept from Java and has
developed it further. Most notably, C# lets classes offer different
implementations
of the same method if this method is declared in more than one of the
class’s implemented interfaces, and it allows interfaces to make
accessible features of their implementing classes otherwise inaccessible
(so-called explicit interface implementations). The latter is of particular
interest since it allows the implicit interface of a class to be disjoint
from the explicit interfaces it implements2; in fact, the implicit
interface of a class can even be empty, forcing all variables providing
access to the features of a class to be interface-typed. Both properties
of C# are in line with Microsoft’s strong emphasis of the interface-based
programming paradigm [Pattison00], favouring interface over implementation
inheritance.
By today, the interface-as-type concept has emancipated itself to
the extent that whole software architectures (such as the OMG’s CORBA
or Microsoft’s COM) are completely defined in terms of interfaces,
not classes. The expected advantage of this is a better (dynamic especially)
composability of software and – as has been right from the inception
of the interface concept – an insensitivity to changes. However,
in a world of interfaces only, one important aspect of interface-based
programming is missed: the fact the same class can implement several,
otherwise unrelated interfaces at the same time. Trivially, in total
absence of classes the distinction between classes and interfaces as
separate types disappears: it is reduced to the classical separation
of specification and implementation.

Figure 2. a) The two sides of an interface (notation
is UML). b) Different interfaces. Throughout the following, we think of an interface as a named
type specifying a (not necessarily complete) set of features characterizing
all instances whose classes declare to implement the interface. Technically, “features” is
commonly restricted to methods, but conceptually, they include attributes – access
to which can be granted through accessor methods as part of the interface – as
well.
3 BASIC PROPERTIES OF INTERFACES
In order to be able to describe the different patterns of interface
utilization in Section 4, we need a certain vocabulary that covers
the different properties of interfaces and their relationships to implementing
classes. The list given below may appear random at first glance (and
is certainly incomplete as regards the general properties of interfaces);
yet it covers all we need to discern the different uses of interfaces
as types.
Caller and Called: The Two Sides of an Interfacey
Every interface has two sides: it separates the caller and the called,
the two roles of the asymmetric calls relationship. In UML jargon,
the caller depends on the interface, and the called implements it (Figure
2 a). However, this naming is problematic, since the true dependency
may sometimes be inverted (see below). Note that although caller and
called are usually associated with classes, it is really objects that
play the corresponding roles.
In the context of components, interfaces are usually classified as
either provided or required (or ingoing and outgoing). Unlike caller
and called, however, provided and required do not refer to two sides
of the same interface, but to two different interfaces, by naming their
roles with respect to a single component (Figure 2 b). Although both
the provided and the required interface have a caller and a called
side, these roles do not refer to the same components: in fact, the
caller of a provided interface can be another component than the called
of a required interface. Note that if dependency of two components
is mutual, this has to be expressed by two opposing interfaces, with
roles swapping depending on the interface being looked at.
In software engineering jargon the caller of an interface is often
called the client and the called the server. This naming is biased
towards a certain use of interfaces, though; in fact, calling in the
above (technical) sense is not necessarily tantamount to requesting
a service, at least not in the original sense of word (which would
imply the benefit of the call to be on the side of the caller). Rather,
as will be seen it may be the case that the caller calls the called
for the latter’s own profit, inverting the roles of client and
server.
In an object-oriented program, callers of an interface are marked
by variables declaring that interface as their type. The called on
the
other hand are the classes (or, rather, their objects) implementing
the interface, be it directly or indirectly. Elsewhere we have defined
metrics counting the number of call sites (so-called Interface
Popularity,
IPOP) and implementations (so-called Interface Generality,
IGEN) [Mayer03, Steimann03, Gössner04]; these metrics have been used
to compute the numbers of
Figure 1 and, as will be seen, they also provide important hints for
the distinction between the different usage patterns of interfaces.
Interfaces and Contracts
An interface is sometimes regarded as the specification of a contract
between a client and a supplier [Meyer97]. Both sides of the contract
have benefits and obligations: the client is obliged to adhere to the
preconditions associated with a needed service and benefits from the
supplier ensuring the postconditions, whereas the supplier benefits
from the client’s keeping to the preconditions and is obliged
to guarantee the postconditions. Although the names client and supplier
suffer from the same problem as client and server mentioned above,
the notions of benefit and obligation are somewhat more neutral; they
will play an important role in our distinction of the different kinds
of interfaces.
Preconditions and postconditions specify the functional (or behavioural)
part of an interface. Depending on the particular kind of interface,
they can be tight (as reflected in lengthy prose or complex logical
expressions) or loose (as loose as constraining only formal
parameter
and return types of a single method). Generally, the laxer a contract
is, the more freely a supplying class can behave. This goes as far
as allowing the implementing class to do anything, as is for instance
the case with the Runnable interface of the
JDK. To the other extreme, interfaces specifications can be so tight
that the only allowable variability
in different implementations is in the non-functional requirements
(such as time or space consumption). This variability is also an important
criterion for the distinction of the different patterns of interface
use as presented below.
Total and Partial Interfaces
The classical interface separating specification from implementation
comprises all public features of its implementing class(es). We call
such interfaces total interfaces3. However, totality of interfaces is
not always a desired property. Quite to the contrary – the Reference
Model of the Open Distributed Processing standard (ODP RM) defines
interface as follows:
Interface: An abstraction of the behaviour of an object that consists
of a subset of the interactions of that object together with a set
of constraints on when they may occur. Each interaction of an object
belongs to a unique interface. Thus the interfaces of an object form
a partition of the interactions of that object. [ISO]
Although we find the strict partitioning (i.e.,
the non-overlapping of interfaces) required in this definition debatable,
we agree that
partiality of an interface (which can have different causes and may
serve different purposes) is an important concept; we call such interfaces
partial interfaces. Note that whether or not an interface is partial
is not a property of the interface alone, but equally determined by
its implementing class(es). In fact, an interface can be both total
and partial (but only of different classes). Thus, when speaking of
a total interface without referring to a particular implementing class
we require that it is a total interface of all implementing classes.
It follows that absolute totality is not a property that can be attributed
to interfaces of open systems, unless there are language means to prevent
implementing classes from adding features.
Interfaces and Abstraction: Generalizations vs. Roles
By definition, an interface is always an abstraction: it abstracts
from implementation by providing its specification.
In object-oriented programming, a supertype abstracts from its subtypes
by omitting some of their features (so-called generalization). The
interface of a generalized supertype is partial in the sense that it
does not cover all public features of all its implementing classes.
Induced by generalization, this partiality is the result of grouping
together classes that share considerable commonality, differing only
in some detail (with the opposite of generalization, specialization,
adding the detail that distinguishes one subtype from the other).
Partiality does not necessarily result from generalization, however;
instead, a partial interface may be designed to isolate a certain aspect or facet of its implementing classes. Although such a partial interface
is still an abstraction, it does not omit detail, but instead focuses
on it (by omitting all features that are not associated with the aspect).
Partial interfaces of this kind reduce the generality of objects, by
focussing on a specific use or appearance in a specific context. Such
interfaces are context-specific; as elaborated elsewhere, they can
be equated with roles [Steimann00b, 01b].
It is not always easy to distinguish a role from a generalization.
In fact, even though there is a fundamental conceptual difference between
the two, it is hard to tie it to formally observable properties, since
the programmer – in expressing her/his intent – is free
to (ab)use the programming language constructs at hand any which way
(s)he pleases. One possibility is to assume that intent is expressed
literally, i.e., that roles have role names (like Customer, Printable,
etc.) and generalizations have genera names (like Thing,
Document,
etc.). However, there is no law to enforce this semantic naming convention,
so that it cannot be relied on. Another possibility is to apply ontological
criteria: if the interface is defined in the context of some relationship
and access to an object through the interface is transient in nature,
then it must be a role [Steimann 00a]. However, this requires a deep
understanding of the use of an interface in a program, and that this
use is stable across all possible extensions, which is seldom the
case. Last but not least, one could argue that if an interface is partial,
is not the only abstraction of the class, and is not extended by some
other interface of the same class (excluding the implicit interface
in the case of Java, since this interface automatically extends all
implemented interfaces of the class), then it is a role. This seems
justified because one must suspect that there are contexts in which
the class is used differently than allowed by the interface (namely
through the other existing abstractions), making the interface (which
is not itself a generalization of some other interface) context-specific.
However, cases can easily be constructed where this (technical) criterion
is misleading so that in practice, a combination of all three has to
be applied.

Figure 3. Classification of interfaces. Offering and
enabling are complementary categories, as are general and context-specific.
Note that as generalizations, interfaces compete with abstract classes.
In fact, in practice interfaces and abstract classes are sometimes
used as if they were the same concept. However, since abstract classes
have the potential to pass on implementation to their subclasses,
they should be used if (and only if) the relationship to the subclasses
is genetic, i.e., if it is (or at least could be) based on the inheritance
of internal structure, that is, implementation. If on the other hand
the relationship is based on pure function (or, weaker still, on
sameness
of protocol), interfaces should be used. For instance, a linked list
and a dynamic array would normally not be genetically related (i.e.,
have no common pieces of implementation), yet they share the interface
of lists (specifying sequential access to their elements).
4 CLASSIFICATION OF INTERFACES
While the technical definition of the interface concept is unambiguous,
its actual utilization can differ greatly. In trying to order the different
usage patterns of interfaces, we have set up the classification shown
in Figure 3. Although it has no strict hierarchical structure (it is
not a tree, but built on two orthogonal subclassifications), the remainder
of this section assumes a linear ordering, progressing through it from
left to right and introducing the different uses along the way. The
classification is based as much as possible on the terms and definitions
presented in Section 3; one should keep in mind, though, that programming
has many degrees of freedom, and any attempt to classify working code
into a set of academic categories must either fail or suffer from a
certain elusiveness.
Offering Interfaces
According to its standard conception, an interface publishes some
service offered by the called to the caller. We call such interfaces
offering.
The benefit of the call to an offering interface is clearly on the
side of the caller, whereas the greater part of the obligations are
on part of the called. This is usually reflected in rather specific
postconditions for the offered services, whereas the preconditions
tend to be brief (and are formulated mostly in the server’s terms).
For instance, while a stack must not be empty in order to pop it (the
precondition), actually popping it requires that the most recently
pushed element be removed, that the second be popped next, etc. (the
postcondition). As an immediate consequence, there is usually a rather
large number of potential callers to an offering interface (since the
preconditions are easily fulfilled), whereas there is typically only
a rather small number of implementors (because they must all fulfil
the same specification, there will usually only be few alternative
implementations).
Depending on whether an interface is intended for the general public
or for specific clients, we distinguish between general and context-specific
interfaces General Interfaces
General interfaces are defined with no particular caller in mind.
Because their purpose is to offer all services of their implementing
classes
to the general public, general interfaces are typically total.
This is so because if not, one must suspect that there are contexts
in
which a class is used differently than allowed by the interface
(as evidenced
by the calling of methods excluded from it), making the interface
context-specific.
Unless general interfaces are related by subtyping (i.e., one
interface is a subinterface of the other), a class usually
has only one general
interface. This is so because there is no point in keeping apart
interfaces that serve no specific caller or use. In fact, the
only purpose of
a general interface is to separate specification from implementation,
making the latter (ex)changeable without affecting its callers.
Depending on whether changes to the implementation reflect
historical or concurrent
(competing) alternatives, we further divide general interfaces
into idiosyncratic and family interfaces.
If a general interface is implemented by only one class (whose
implementation may be modified over time, but with no two alternatives
occurring
in the same project), we call this interface idiosyncratic4.
Idiosyncratic interfaces are often named after the classes they specify
the
interface of, with either the interface or the class name
being complemented
by a prefix or suffix indicating the interface or implementor
status, as in IPerson or StackImpl.
Idiosyncratic Interfaces. An idiosyncratic
interface use is characterized by a to-one relationship of a total
interface
with its implementing
class: only one class implements the interface at a time.
(The class may however implement other interfaces, but these interfaces
will
typically be context-specific.) Because the interface
is a
complete abstraction
of the class it implements, it can replace for the class
in variable declarations5.
This is the classical use of the interface-as-type
concept.
|
|
Figure 4. Idiosyncratic interface |
Figure 5. Family interface |
Figure 4 illustrates the use of an idiosyncratic interface: a single class,
the server, implements a total interface on which its (unspecified)
clients depend. Because the clients are unspecified, later introduction of
new clients
(with perhaps new uses of the server) do not require new interfaces;
the idiosyncratic interface is completely unspecific, that is, general.
In contemporary languages such as Java and C#, idiosyncratic interfaces
will mostly remain implicit, because the set of features declared by
a class as public can be thought of as its interface (the implicit
interface; cf.
Section 3). Factoring out theses features into a separate syntactic
entity may not seem worth the effort, particularly since it is implicitly
done by
the compiler. Therefore, we cannot expect to find many idiosyncratic
interface declarations in Java or C# program corpora. This is in contrast
to the instructional
literature on object-oriented programming, in which idiosyncratic interfaces
(such as IDog or IPerson) abound.
As it turns out, there are no true examples of idiosyncratic interfaces
in the JDK – the implementations of literally all interfaces implemented
by only one class are tagged as examples. Things are different for
Eclipse, however, which has a use for idiosyncratic interfaces even
in Java programming: its IClassFile for instance is the interface of
only one class,
ClassFile,
of which it is a total interface. To quote the documentation, this
interface “represents
an entire binary type (single .class file)” and is “not intended
to be implemented by clients”. In fact, ClassFile resides in a package
explicitly marked as being internal to the system (org.eclipse.jdt.internal.core),
contrary to its interface, IClassFile, which is from the publicly accessible
package org.eclipse.jdt.core.
If a general interface is implemented by more than one class at the
same time (i.e., within the same project), we call this interface a
family interface (Figure 4). The different classes are often offered
as alternative implementations, with different (technical) properties;
yet each one
adheres
to the same interface specification. As opposed to idiosyncratic interfaces
whose implementation can be varied only at design time of the program,
family interfaces offer implementation alternatives at run time; they
are therefore
often accompanied by factories [Gamma95] and double dispatch as a substitute
for multiple dispatching [Steimann01a].
Family Interfaces. A family interface heads a family
of classes, offering their services to the general public (i.e., unspecified
clients). If
the classes comprised under the family interface extend each other,
it will be a generalization of some of the classes, but these usually
have their
own (idiosyncratic or family) interfaces.
Because family interfaces
are context-independent, they specify the nature of their objects rather
than their behaviour in a specific role
(cf. Section 3). Family interfaces therefore often carry typical class
names (such
as Number or Interval) and are used interchangeably with abstract classes
as patres familiae of a family of classes [Steimann01a]. In fact, in
absence of multiple (class) inheritance interfaces are sometimes abused
(as regards
the criteria of Section 3) to root class hierarchies where abstract
classes would be in place, in order not to block the possibility of
inheritance from
other classes6.
Prominent family interfaces are Collection, Set, List,
and others from the Java 2 collections framework. Note that Enumeration and Iterator, which are mostly implemented by anonymous classes7 and
by classes inner
to the corresponding
collections, are also family interfaces: although conceptually both
interfaces are context-specific (providing sequential access to collections
in the context
of iteration), technically they are not – there is no other use of
enumeration and iterator objects.
Because family interfaces are often only partial interfaces of some
of their implementing classes, the demarcation from client/server interfaces
(as discussed next) is sometimes difficult. For example, an instance
of class TreeSet may be used as a plain collection in some places and
as a sorted
set in others, making its use context-specific. However, since Collection is a generalization (of both SortedSet and TreeSet), it does not focus
on specific properties; hence, it will usually not be seen a role.
Context-Specific Interfaces (Roles)
An interface that is not general is context-specific; it comprises
the specific protocol expected by certain callers whose relationship
to the called (the implementors of the interface) sets up the context
of their (the
implementors’) use. Because specificity is a property defined only
in presence of variety, context-specific interfaces are typically partial.
However, they may overlap.
Although it is conceivable that context-specific interfaces are also
idiosyncratic (i.e., the interface of only one class), one general
theme behind context specificity is to minimize coupling, allowing
a greater number
of classes to take the place of the called. A distinction based on
the count of implementors of a context-specific interface (as done
for general interfaces)
is therefore not useful. Instead, there is an interesting twist to
the dependency relationship, differentiating offering from enabling
interfaces (Figure 3).
For the obvious kind of context-specific interfaces, the caller relies
on some specific service offered by the implementor of an interface:
being the beneficiary of the interaction, it is the client of the service.
On the
other side of the interface, the called behaves as the benefactor:
it is the server of the service (Figure 6). We therefore call such
interfaces client/server
interfaces. As for general interfaces, client/server interfaces usually
come with tight contracts so that the called can often be substituted
without affecting the caller’s or global program behaviour (cf. Section
3).
Client/Server Interfaces. A client/server interface is a context-specific
interface that offers the particular services needed by certain clients.
The server typically has other features not included in the client/server
interface; it offers different interfaces to dif-ferent clients.
Although client/server interfaces will typically be found in closed
application programs in which both client and server have been designed
specifically to interact with each other, they can specify partitioned
access to general
purpose classes as well. For instance, an interface Stack could be
used to specify access to an instance of class Vector in all contexts
in which it
is used as a stack, without prohibiting the use of the same or other
instances of the same class as something else (a queue for instance)
in other places.
A typical example of a client/server interface is the use of the MenuContainer interface from the JDK (package java.awt). The interface provides functions
for “all menu related containers”; it is implemented by classes
as different as Button, Checkbox, Scrollbar, and TextFrame, all of which
can – despite their different nature – present as menu containers
to their clients. Clients rely on this particular property by accessing
their servers through the MenuContainer interface; the classes implementing
MenuContainer,
however, have many other services to offer.
Other prominent client/server interfaces identified in the JDK are
Shape and LayoutManager (both from java.awt), DataInput and DataOutput (from java.io), and java.lang.CharSequence. Interestingly, some of
these interfaces
would appear to be typical family interfaces: whereas DataInput,
DataOutput, and CharSequence qualify as role names (names given in
a certain context),
Shape and LayoutManager sound like the names of natural types or
generalizations (and hence would be expected to be family interfaces;
cf. the argumentation
of Section 3). However, the classes implementing Shape are really
quite heterogeneous, with Shape specifying only one aspect common to
all of these; not as consistently,
but in a similar vein, LayoutManager is an interface of user interfaces,
editors, and layout managers, focusing only on the layout manager
aspect of each.
Enabling Interfaces
For general and client/server interfaces (the interface uses presented
so far), the class implementing the interface is the service
provider. However, as indicated above this need not always be so. In
fact, there is an important
category of interfaces in which the caller of the interface is
the service provider (server) and in which the called is either
the client or an item of it. Because these interfaces enable the
called object’s individual contribution to or participation in the
caller’s context, we call them enabling interfaces.
|
|
|
Figure 6. Client/server interface. Server offers its services to different
clients, each with its own client/server interface, comprising only what
is needed. |
Figure 7. Server/client interface. The receivers of the service are
the implementors of the interface. |
Figure 8. Server/client interface. The receivers of the service are
the implementors of the interface. |
Enabling interfaces often carry an -able or -ible suffix, as for example
Printable, Accountable, or Accessible.
Characteristically, postconditions of enabling interfaces are not as tight
as those for offering interfaces,
so that exchanging one implementor for another will most likely alter
program behaviour. In fact, different implementing classes of an enabling
interface
are typically carriers of different, application-specific code. This
is in contrast to the caller, which will often be general purpose and can
be substituted
accordingly.
Depending on whether the called object or some third party is the beneficiary
(client) of the service, we have subdivided enabling interfaces further,
namely into server/client and server/item interfaces.
With server/client interfaces, the beneficiary is the called object,
which profits from a service offered by the caller of the interface
(the roles of client and server thus being swapped, as in Figure 7).
As an immediate
consequence, the contract for the implementing class is usually rather
lax (as reflected by a fairly small number of methods required); that
of the
caller remains mostly implicit.
Server/Client Interfaces. A server/client interface is an interface
that enables its im-plementors to profit from some service offered
by the caller. A server/client interface is often specific to a particular
server;
however, it need not be.
Not coincidentally, the naming suggests that server/client
and client/server interfaces oppose each other: if interaction between
client and server
is bidirectional (with roles of the participating object being independent
of
the particular direction), these interfaces are likely to occur in
pairs (Figure 8). Such is typically the case in asynchronous communication,
when
callbacks are required to return the value of a computation; however,
it also occurs when the server needs additional information from the
client not provided with the initial service request.
|
|
Figure 9. Server/item interface |
Figure 10. Service relying on a server/item
interface, made available
through a client/server interface. |
Although paired client/server-server/client interfaces are common,
server/client interfaces exist in their own right. For instance, the
observer role of the Observer pattern [Gamma95]
is represented by a server/client interface implemented by the beneficiary
of the collaboration,
namely
the
object to be notified when a change occurs. Typical server/client interfaces
from the JDK are thus its listener interfaces. Other typical instances
of server/client interfaces occur in frameworks, where the execution
of user-provided classes (so called plug-ins) is controlled by a set
of framework classes
(a condition referred to as inversion of control [Fayad97]). Being
enabling
interfaces, it is the implementing class (the client) which provides
the application-specific code; the calling class (the server) on the
other hand
is typically application independent (for application frameworks: independent
of a specific instantiation). Note that although server/client interfaces
can in principle be total, they are usually only partial since the
plug-in classes need to interact with others to fulfil their application-specific
purpose, of which the server has no knowledge.
The prototypical example of a framework server/client interface is
the Runnable interface of the JDK (package java.lang),
providing for multithreading in Java. A class
implementing Runnable does this because
it wants to run
a separate (its own) thread so it can act independently of others,
and it receives this thread (the service) from Thread,
the class calling the Runnable interface.
The contract for Runnable is minimal: it consists
of a single method run with no arguments. Even
semantically, this method is totally unconstrained: to quote the JDK
documentation, “[t]he
general contract of the method run is that it may take any action whatsoever.” Other
prominent server/client interfaces with similar protocol are Action and MenuElement,
both from the Swing GUI framework.
The relationship between the server and the client of a server/client
interface is typically rather long-lasting. As it turns out, server/client
interfaces can often be identified by the fact that the server offers
a special procedure for registering its clients (and a corresponding
attribute keeping
references to them). This holds particularly for plug-ins and observers;
it does not, however, apply to the links held by a server calling
back its clients, which are typically temporary in nature8.
For many enabling interfaces the beneficiary of the service is not
the implementor (the called) itself, but some other class holding
it. Typical examples are the -able interfaces Comparable and Accountable;
for instance,
it is not the comparable object which profits from being compared,
but the object holding it, as for example a collection that is
to be
sorted (or,
ultimately, the owner of the collection). Because the implementing
object is typically an item of some other class, we call these
interfaces server/item interfaces.
Server/item interfaces occur in collaborations of three or more
objects in which one object (the item) is passed by another (the
client) to a third (the server) which is to process the item for
the client (Figure
9). This
processing requires some support from the item, which offers
this support
by implementing the server/item interface. As opposed to server/client
interfaces, the relationship established between the caller (the
server) and the called
(the items) of a server/item interface is temporary in nature.
Therefore, server/item interfaces typically occur as the types
of formal
parameters
and temporaries, but not of attributes (fields).
Server/Item Interfaces. A server/item interface is an interface
that enables the proc-essing of a certain kind of objects (the
items) by a server for the profit of some third party, the
client.
Server/item interfaces are frequently found in
conjunction with offering (client/server especially) interfaces. The
offering interface then
includes the server/item interface as a formal parameter
type (Figure 10). Note that
in the example of sorting collections, this would require
a generic
type (a List of Comparables).
Other prominent server/item interfaces are Printable (from
java.awt.print), LazyValue (inner to javax.swing.UIDefaults)
and XMLWritable (from
org.apache.crimson.tree). Note that in the case of Printable,
one might argue that it is the
printable object itself that profits from being printed.
However, unless the object
and the printer are related somehow (which is typically
not the case for implementors of Printable), some third party
must be the issuer (and beneficiary)
of the printing request.
Special cases of server/item interfaces are the so-called
tagging or marker interfaces9.
Tagging interfaces are often empty, in
which case they can only occur as parameter (and not
as receiver) types of
method
calls. The dependency arrow from the server to the server/item
interface in
Figure
10 then stands for a dynamic type check (usually in the
form of an instance-of test), the primary purpose of
a tagging interface10. On
the
other hand, many
tagging interfaces are abstract in the sense that they
are
not directly implemented, but root a hierarchy of non-empty
interfaces, or they
extend non-empty interfaces
without adding functionality.
Figure 11. Relative distribution over the five different
uses.
Relative Distribution of Interfaces
To probe the relative frequency of the different kinds
of interfaces, we looked at the 100 most often implemented
and at
the 100
most often referenced (as counted by the number of
variables) interfaces from the JDK 1.411.
Between the two groups, there was an overlap of 43. One interface
contained only constants; we excluded it from our
list because it did
not serve as a type.
Among the remaining 156 interfaces, there are no
idiosyncratic and only 30 family interfaces (together
comprised as
general interfaces; of these, only four are total
interfaces of
all their implementing classes); the remainder
(126) is context-specific, i.e., classified as
roles. Among these, less than half (56) are client/server
interfaces; the rest (68)
was
categorized as
enabling interfaces, namely 42 as server/client
and 26 as server/item. Among the 42 server/client interfaces
22 are
listeners.
As can be seen from Figure 11, offering (i.e.,
family and client/server) interfaces dominate
over enabling interfaces, but this dominance
is not as clear as one might have expected. This
may be
due to our selection criteria,
which placed equal weight on number of implementations
and number of
variables (or call sites; cf. Footnote 11). In
fact,
more than half of the most often
implemented interfaces were classified as enabling,
compared to only one third of the most often
referenced (which lead in the offering category). This should
come as
no surprise,
though, since clients
are likely to
be more
common than servers, meaning that offering interfaces
should
be
referenced
more often than they are implemented, whereas
enabling interfaces (with roles of client and server inverted)
should be implemented
more often than they
are referenced.
Table 1: Average number of implementations (IGEN), variables (IPOP),
and methods for each category of interfaces. 
Investigating this relationship further, Table
1 reveals that average values for IGEN and
IPOP as well as
their
quotient distinguish pretty well between offering
and enabling interfaces:
in fact,
on average
there is less
then one variable per implementing class of
an enabling interface, while there are almost four
per class for each offering interface.
Also, the average
size of the interfaces (as measured by the
number of methods) is significantly larger for offering
than
for enabling interfaces; this is
in accord
with the assumption that the contract for enabling
interfaces is much weaker than
for offering (general and client/server) interfaces
(cf. Section
3). Note that average interface size does not
differ much
between family and client/server;
while this may be partly due to the fact that
the boundary between these two in open general
purpose libraries such as the JDK is
rather blurry, we
have observed elsewhere [Mayer03b] that client/server
interfaces tend to be not
as context-specific (narrow) as they could
be, including more methods than actually needed.
We would have liked to present a table analogous
to Table 1 here with measures for formally
differentiating general
from context-specific
interfaces, but were unable to identify robust
decision rules. This is in line with the
already mentioned (in Section 3) lack of
sharp criteria distinguishing generalizations from
roles.
5 DISCUSSION
Related Work
The general literature on object-oriented software engineering and
programming strongly advocates the use of interfaces. However, little
is written about how interfaces are to be identified and introduced
systematically. Coad’s book about the design of Java applications
[Coad99] and one about programming with COM [Pattison00] are noteworthy
exceptions; note that the latter is also one of the few sources using
the term interface-based programming.
In his lengthy treatment of the design with interfaces, Coad has
identified mainly two different categories: “the kinds of
classes whose
objects you want to plug into that plug-in point”, and “the
kinds
of behaviour you want such objects to exhibit”. In the
latter category, interfaces contain “little groupings of functionality” within
a broader “kind of class” classification. This category
is subdivided into interfaces indicating an algorithm’s plug-in
points (enabling interface in our terminology) and interfaces indicating
that a so-called feature sequencer is expected at a given point. [Coad99]
Note that although Coad discusses the relation of roles to interfaces,
he views roles as suggested by the Role Object
Pattern [Bäumer97].
By contrast, we have equated roles with context-specific interfaces
[Steimann00b, 01b], a standpoint that is supported by the fact that
these interfaces often carry role names (e.g., the -ables and -ibles),
and that patterns themselves are defined in terms of roles (including
the Role Object Pattern, which cannot
be applied to itself) [Steimann00a].
The literature describes other programming languages that emphasize
interfaces over classes. For instance, Emerald has no classes (only
constructors); it uses interfaces as types in variable declarations
[Raj91]. Type checking in Emerald (as required for variable assignments)
is performed statically wherever possible; where not, a run-time
type check is inserted and executed dynamically to ensure that further
operations
(method calls) are safe. However, since interfaces are the only kind
of type in Emerald, there are no interface-specific usage patterns
to be observed.
Why Java and the JDK?
We chose to look at Java programs because the Java language (like
C#) offers both abstract classes and interfaces as syntactically
distinct
constructs, giving the programmer the opportunity of being explicit
about her/his intentions: although completely abstract classes
and interfaces can be (and sometimes are) used interchangeably
to a certain
extent, we expect the resultant classification errors to be small,
especially when compared to the errors induced by manually deciding
whether an abstract class was conceptually intended to be an
interface or the root of a class hierarchy, as we would have had to
do, for
instance, with C++ class libraries.
We selected the JDK mainly because it is rather well-known to
a wide audience, because it is well-documented, and because
an archive
of
older versions is available. We have also looked at other large
and freely available packages; of these, Eclipse appears to
make the
most disciplined use of interfaces, but its API is known only
to a smaller
audience and examples would have required much more explanation.
Note that it is generally difficult to classify the interfaces
of an open API such as the JDK as we did, because utilization
of the
offered
types in actual applications is somewhat unpredictable. However,
since the JDK is also a framework making extensive use of
its own classes
and interfaces, we are confident that the derived numbers
have some practical relevance.
6 CONCLUSION
While there seems to be a certain consensus that the introduction
of interfaces as types syntactically distinct from classes is a good
idea, only little work has been spent on the investigation of their
practical use. The reason for this apparent lack of interest may
be that the concept seems too simple to be subject to examination.
On the other hand, we could identify a number of fundamental properties
distinguishing different kinds of interfaces, and derive guidelines
for their systematic use. A set of five basic patterns of interface
utilization together with figures indicating the frequency of their
occurrence are the results of our work.
Footnotes
1 The language Emerald [Raj91]
is another possible role model for the introduction of interfaces
as types in Java: in Emerald, all
variables must be interface-typed, and classes are completely replaced
for
by (implicitly
typed) constructors.
2 To be more precise, the
implicit interface of a C# class does not automatically extend
the interfaces the class implements.
3 As noted above, an interface
specifies features of objects, not of its implementing classes.
Since constructors are class methods, the fact that an interface
does not include constructors does not mean that it is not total.
4 Note that general relates
to the caller’s side of the interface (indicating that it
is not aimed at a specific caller), whereas idiosyncratic pertains
to the implementor’s: only one class has this interface.
Hence, there is no contradiction in calling a general interface
idiosyncratic.
5 Note that if language peculiarities
prevent an idiosyncratic interface from being a complete abstraction
of the class it represents, it cannot ubiquitously replace for
that class in variable declarations. In the case of Java, if direct
access to the fields or non-public or static features of a class
is required, this prohibits the use of an interface in the class’s
place.
6 In fact, the Java API specification
is full of Freudian slips confusing classes and interfaces: for
example, the interface MenuContainer is defined as “the super
class of all menu related containers”, and Streamable as “the
base class for the Holder classess of all complex IDL types”.
7 Java’s anonymous class
mechanism allows the ad-hoc creation of instances whose protocol
conforms to an interface. If this interface is exclusively “implemented” by
anonymous classes, then it designates the only context in which
the instances can be used, and it is necessarily total so that
it must be classified as a family interface. (It is not idiosyncratic
because arbitrarily many different alternative implementations
can be provided, as is the case for Enumeration.)
8 Note that in the common
type system (CTS) of Microsoft’s .NET framework, both callbacks
and observers are realized by so-called delegates, special types
representing function pointers.
9 The names are to express
that the so-labelled interfaces flag their implementing classes
as being of a certain type. This information can be utilized by
the compiler (through type checking) as well as during program
execution (by querying an object’s conformance to the interface).
Quite obviously, tagging interfaces share this property with all
others.
10 Tests for identity are
another possible operation for tagged objects. Characteristically
enough, however, the JDK’s most frequently implemented empty
interface, Serializable (implemented
1975 times in the JDK 1.4) grants access to all attributes of its
implementing classes, albeit only through
introspection.
11 We classified all interfaces
manually, by looking at the source code and documentation. Quite
clearly, there was no way of doing this for all of JDK 1.4.1’s
857 interfaces. In order to include the most interesting interfaces
and at the same time keep the bias small, we decided to pick those
with the highest numbers of implementations and variables, respectively.
However, as it turned out the ratio of these counts separates offering
and enabling interfaces pretty well, so that there is a certain
bias towards an equal distribution of our sample between these
two groups. REFERENCES
[Bäumer97] D Bäumer, D Riehle, W Siberski, M Wulf “The
role object pattern” in: PLoP ´97 – Proceedings
of the 1997 Conference on Pattern Languages of Programs (1997).
[Coad99] P Coad, M Mayfield JAVA Design: Building Better
Apps and Applets 2nd edition (Yourdon Press, Upper Saddle River 1999).
[Fayad97] ME Fayad, DC Schmidt “Object-oriented application
frameworks” CACM 40:10 (1997) 32–38.
[Gamma95] E Gamma, R Helm, R Johnson, J Vlissides Design
Patterns – Elements
of Reusable Software (Addison-Wesley, 1995).
[Gößner04] J Gößner, P Mayer, F Steimann “Interface
Utilization in the Java Development Kit” in: Proceedings
of the 19th ACM Symposium on Applied Computing SAC 2004 (Nicosia,
Cyprus, 2004) 1310–1315.
[IEEE] IEEE Standard
Computer Dictionary (IEEE, 1991). [ISO] ISO/IEC Open Distributed Processing – Reference
Model – Part
2: Foundations International Standard 10746-2 /ITU-T Recommendation
X.902
[Liskov77] B Liskov, A Snyder, R Atkinson, C Schaffert “Abstraction
mechanisms in CLU” CACM 20:8 (1977) 564–576.
[Mayer03a] P Mayer Eine Metrik-Suite zur Analyse des Einsatzes
von Interfaces in Java Bachelor Thesis (Universität Hannover,
September 2003).
[Mayer03b] P Mayer “Analyzing the Use of Interfaces in Large
OO Projects”. in: OOPSLA 2003 Companion (Anaheim, USA , October
26-30, 2003) 382–383.
[Meyer97] B Meyer Object-Oriented Software Construction Second Edition (Prentice Hall, 1997).
[Pattison00] T Pattison Programming Distributed Applications
with COM & Microsoft Visual Basic (Microsoft Press, 2000).
[Raj91] RK Raj, ED Tempero, HM Levy, AP Black, NC Hutchinson,
E Jul “Emerald: A General-Purpose Programming Language” Software – Practice
and Experience 21:1 (1991) 91–118.
[Steimann00a] F Steimann “On the representation of roles
in object-oriented and conceptual modelling” Data & Knowledge
Engineering 35:1 (2000) 83–106.
[Steimann00b] F Steimann “A radical revision of UML’s
role concept” in: E Evans, S Kent, B Selic (eds) UML
2000: Proceedings of the 3rd International Conference (Springer, 2000)
194–209.
[Steimann01a] F Steimann “The family pattern” Journal
of Object-Oriented Programming 13:10 (2001) 28–31.
[Steimann01b] F Steimann “Role = Interface: a merger of concepts” Journal
of Object-Oriented Programming 14:4 (2001) 23–32.
[Steimann03] F Steimann, W Siberski, T Kühne “Towards
the systematic use of interfaces in JAVA programming” in:
Proceedings of 2nd Int. Conf. on the Principles and Practice
of Programming in JAVA (Kilkenny, 2003) 13–17.
About the authors
 |
|
Friedrich Steimann is a full professor for Programming Systems
at the Fernuniversität in Hagen, Germany. He leads a research
group on software modelling, programmers’ productivity,
and object-oriented development tools. He can be reached as
steimann at acm.org.
|
 |
|
|
 |
|
Philip Mayer is a graduate student of Computer Science at the
Universität Hannover. In his Bachelor's Thesis he developed
an extensive metric suite measuring the use of interfaces in
Java programs. He is one of two developers of intoJ, an open
source Eclipse plugin analyzing the access of types. He can
be reached at plmayer@acm.org.
|
Cite this article as follows:Friedrich Steimann and Philip Mayer: "Patterns
of Interface-Based Programming",
in Journal of Object Technology, vol. 4, no. 5, July-August 2005,
pp. 75-94 http://www.jot.fm/issues/issue_2005_07/article1
|