Authors : Alex Shindich, Curt Hagenlocher
Establish an interface abstraction implicitly by using functional polymorphism in the cases where the use of explicit interface constructs is either unavailable or would cause excessive refactoring of object-oriented code.
Most programmers make use of the Implied Interface pattern without even realizing it. In fact, one could argue that when applied to a particular language, the pattern turns into a language-specific idiom. The inherent simplicity of this pattern causes an unfortunate side effect – implied interfaces are rarely acknowledged as such and therefore are almost never documented.
The purpose of this paper is to promote the notion of the Implied Interface to a first-class pattern. It is the authors' hope that the implied interfaces will be better documented once the existence of the pattern is widely acknowledged.
Suppose that we are interested in developing an algorithm or library of algorithms that would operate unchanged on a vast majority of existing code, and thus appeal to a large group of potential users.
In decreasing order of generality, such an algorithm might operate on
The corresponding disadvantages of these targets are
Let's analyze the solution with an explicit algorithm interface and a class adapter.
As we can see from the class diagram (Fig 1.a), the use of class adapter will lead to creation of a parallel hierarchy of adapters. This solution does not work with the forces that disallow extensive modifications to existing class hierarchies, and demand to minimize the development time.
An alternative way of implementing the Algorithm Interface is to use an Interface Adapter (Fig 1.b). Unfortunately this would require that existing classes had a common base interface, which would preclude a large number of existing/legacy systems from being able to use our algorithm implementation.
Around 1990, the state of the art in C++ class libraries was the NIH Class Library. This included a number of useful containers, each of which required that the objects being contained be a subclass of a fairly wide Object class. This is an example of category 3, above. Despite the high quality and usefulness of the code, it did not see widespread usage at least in part because of its incompatibility with existing and new code that did not derive from the same Object class.
We can achieve greater success by recognizing that there is a fifth possibility that fits between the first two in its level of generality. We do this by taking advantage of functional polymorphism. Functional polymorphism is a construct that allows writing generic algorithms that can operate on any data type that implements methods and properties used in the algorithm. Functionally polymorphic methods and properties are bound by name. Depending on the language, the binding is performed either at compile time or at runtime.
By 1996, the state of the art in C++ libraries was the Standard Template Library -- STL. STL, now in widespread use, takes advantage of the template features of C++ to implement generic algorithms. An STL class or algorithm operates on another class T. In doing so, it defines an implied interface for the target class. In the simplest cases, this implied interface consists only of publicly accessible constructor, copy constructor and destructor. Changes to the target class are required only in very rare cases; in most of these, a generic adapter class can be used.
Because we want widespread use of our code, we will learn from this history, and use functional polymorphisms with implied interfaces to implement the algorithms. In order to improve our chances of success, we will document all the requirements that our library imposes on its clients. In particular, we will define all the implicit interfaces that it uses.
As an additional benefit, the future cost of changing the implicit interfaces that our library uses will be insignificant in comparison to the refactoring costs we would incur had we used explicit interfaces. Consider the following example:
a. A company is trying to define a vendor-independent native COM interface to middleware servers.
b. The authors of the interface considered snapshotting as one of the basic features that any middleware vendor would support. And indeed, the first vendor of choice supports the snapshot capability.
As it can be seen from the above example, changes to the explicit interfaces result in a huge refactoring effort aimed exclusively at satisfying the new look of the old interfaces. If the authors chose implied interface, such as OLE’s IDispatch, over the explicit COM interfaces, the refactoring of the client code would be limited to the areas that make use of the snapshotting function.
Use the Implied Interface pattern when:
Do not use this pattern if checking for compliance with an explicit interface is important.
The generalization arrows are grayed out because they are implied. In reality, the concrete classes do not derive from a common interface.
The Implied Interface pattern has several benefits and drawbacks:
In the languages that have interface support, it is customary to check if an
object implements a particular interface. This check can happen at
compile-time (for strongly typed languages) or at run-time. This check is
not typical when using an implied
interface. While many languages allow discovering whether or not a specific method is present (usually through introspection), the pattern is to assume that it does exist, and to trap the error to handle the case where it does not.
The implementation strategy is extremely simple.
1. Defining an interface. There is no formal syntax for implied interface definition. Using your favorite documentation method, document the methods that make up the interface. For example:
<?xml version="1.0" ?>
doc="Defines an interface to a living creature">
doc="Consumes food to produce the energy for the living creature"/>
doc="Makes creature-specific noise. "/>
2. Modifying concrete classes to conform to the interface. Simply implement the methods documented in step 1. It is not necessary to implement all the interface methods but only the implemented methods will be available to the client code.
>>> class Dog:
def eat (self):
"""eat () -> void
print "Bones are yummy!"
Class Dog partially implements ILivingCreature interface because it only implements eat method.
An equivalent definition in C++ would look like:
void eat () const
std::cout << "Bones are yummy!" << std::endl;
Note that method eat is not virtual.
3. Making use of the interface. The client code simply calls the methods on the instances of the concrete classes. A language-specific error occurs if a concrete class does not implement a particular method.
>>> d = Dog ()
>>> d.eat ()
Bones are yummy!
>>> d.makeNoise ()
Traceback (most recent call last):
File "<pyshell#56>", line 1, in ?
AttributeError: Dog instance has no attribute 'makeNoise'
Note: Python uses introspection to bind methods and properties at runtime. This makes all Python algorithms generic. In C++ generic algorithms are written using templates.
template <class T> void feedCreature (T & const creature)
Bones are yummy!
Trying to compile the following code would result in compile error:
template <class T> void hearCreature(const T & creature)
--------------------Configuration: Dog - Win32 Debug-------------
D:\Dog\Dog.cpp(16) : error C2039: 'makeNoise' : is not a member
x.cpp(4) : see declaration of 'Dog'
x.cpp(24) : see reference to function template
instantiation 'void __cdecl hearCreature(const class Dog &)'
Error executing cl.exe.
dog.exe - 1 error(s), 0 warning(s)
The sample code is going to illustrate the solution to the problem presented in the motivation section.
The goal is to develop a library of generic mathematical algorithms. The library should have an algorithm for computing a sum of elements of an arbitrary sequence. The algorithm should work with a vast majority of existing types, and impose minimal requirements on the algorithm’s subjects.
The first step is to define an implied interface that the sum algorithm will operate on.
<?xml version="1.0" ?>
doc="Defines an interface for adding two objects of the same type">
<Method name="operator +"
doc="Adds two objects of the same type">
type=" unknown type"
doc="The value to be added to the value contained in this instance."/>
The next step is to write the algorithm.
>>> def sum (sequence):
if not len (sequence):
raise “Non-empty sequence expected.”
return reduce (lambda a, b: a+b, sequence)
As you can see, the implementation in Python is straightforward, because the language has built-in support for generic sequences.
The C++ implementation of our algorithm will rely on STL’s iterators.
template <class Iterator, class T>
void sum (Iterator begin, Iterator end, T & out)
if (begin == end)
throw exception ("Non-empty container expected.");
out = *begin;
for (Iterator iter = ++begin; iter != end; ++iter)
out = out + *iter;
The only thing left at this point is to make use of the algorithm.
>>> nums = (1,2,3,4,5,6,7,8,9)
>>> sum (nums)
>>> strs = ['1','2','3','4','5','6','7','8','9']
>>> sum (strs)
An equivalent example in C++ would look like
for (int i = 1; i < 10; ++i)
int result = 0;
sum (nums.begin (), nums.end (), result);
std::cout << result << std::endl;
for (i = 1; i < 10; ++i)
stream << i;
strs.insert (stream.str ());
sum (strs.begin (), strs.end (), strresult);
std::cout << strresult.c_str () << std::endl;
As the examples above demonstrated, the sum algorithm was used successfully with a set of existing types that had an implementation of "+" operator.
The Implied Interface pattern allows polymorphic treatment of objects that are not members of the same class hierarchy; two totally unrelated objects could implement methods with the same names (the exact restrictions vary depending on the implementation language; for instance C++ also requires that method signatures match.) to indicate that they implement the same implied interface. STL is a perfect example of a library that makes use of the implied interface. This technique is also very common in Python.
This pattern is also used in conjunction with Windows DLLs. Each DLL can export a number of functions that are not grouped. DLL's users may choose to only use a subset of the exported functions. More importantly, the client may use two DLLs polymorphically as long as they both export a function with the same name and signature. (In practice, there are more restrictions then simply exporting the same function name from the DLL. Implementations of both functions must use the same calling convention, the same name-mangling scheme, the same version of C++ runtime library, etc. There are other restrictions that are not mentioned here.)
There is an example of Implicit Interface usage that came about during the early days of COM. The (original) COM control spec defined a certain number of "standard" properties to be accessed through automation. These properties were given standard DISPIDs. For instance, DISPID_FONT is -512. This made it possible to set the font without going through the vtbl by using the IDispatch interface with a DISPID of -512.
Many patterns that rely on the notion of interfaces can be implemented using the Implied Interface pattern.
Also see The Abstract Class Pattern.
We would like to thank Jim Stern and John Liebenau for their comments on the pattern. Special thanks go to our PloP shepherd Hans Wegener for his help during the revision of this paper.
Copyright (C) 2001, Alex Shindich and Curt Hagenlocher
Permission is granted to copy for the PLoP 2001 conference. All other rights reserved.
 Keith E. Gorlen, Sanford M. Orlow and Perry S. Plexico, Data Abstraction and Object-Oriented Programming in C++, John Wiley & Sons, Ltd., Sussex, England, 1990.