Interfaces allow you to use selected functionality of a class without needing to know the concrete type. An interface is perhaps the most powerful form of polymorphism. In Java, interfaces are a built-in type. In C++ interfaces are pure virtual base classes. In COM, interfaces appear as an array of function pointers. The Bridge/Impl pattern is a very effective way to simplify the use of interfaces.
When you require someone else to implement an interface, such as a callback, you should oblige them to implement as few methods as possible. Keep the methods simple and their behavior unambiguous. When you provide interfaces for others to use, they want you to provide as many convenient services as possible. Users want extra methods with default arguments, and they want robust tolerance of special cases. Most interfaces eventually have both uses. How can you avoid this conflict in priorities? You need the Bridge pattern.
Begin with the simplest methods necessary to
define your functionality. For a Vector
you might define methods to add another
vector and to scale by a constant. These
methods will constitute your implementation
interface, called Impl for short. Next
define a full-featured interface that has
convenience methods like zero and
copy. Most often, you want to derive the
full-featured interface from the simple
Impl interface, so that simple methods
are still available. The extra methods can
be coded by using only the methods in the
Impl class. You can code these
implementations once and for all in one
place, the bridge class.
In Java your bridge class might look like
public interface VectorImpl {
public void scale(double factor);
public void add(Vector anotherVector);
}
public interface Vector extends VectorImpl {
public void zero();
public void copy(Vector anotherVector);
}
public class VectorBridge implements Vector {
private VectorImpl _vectorImpl;
public VectorBridge(VectorImpl vectorImpl) {
_vectorImpl = vectorImpl;
}
public void scale(double factor) {
_vectorImpl.scale(factor);
}
public void add(Vector anotherVector) {
_vectorImpl.add(anotherVector);
}
public void zero() {
_vectorImpl.scale(0.);
}
public void copy(Vector anotherVector) {
this.zero(); _vectorImpl.add(anotherVector);
}
}
Most of your code will export Vector
interfaces, and you will require users to
implement VectorImpl. VectorBridge
makes a VectorImpl look like a
full-featured Vector.
In C++, you can follow the same style or you can abbreviate with an abstract class. (Some methods have implementations, and others do not.)
class Vector {
public:
virtual void scale(double factor) = 0;
virtual void add(const Vector& anotherVector) = 0;
virtual void zero() {
this->scale(0.);
}
virtual void copy(const Vector& anotherVector) {
this->zero(); this->add(anotherVector);
}
virtual ~Vector() {}
};
Now we have only one interface for a vector.
The three preceding classes have been
combined into one. Users can override the
extra methods if they have a more efficient
implementation. Otherwise, they get a usable
default. On the other hand, if you need only
the Impl methods, a user may
unnecessarily optimize unused methods.
You can code an identical abstract class in Java, but with a serious drawback. Java classes can implement any number of interfaces but can extend only one abstract class. Personally, I prefer to keep all implementation out of interfaces. There are good reasons for avoiding multiple inheritance of implementations. Nevertheless, the simplicity of adding default convenience methods is very tempting.
Bill Harlan, 1999
Return to parent directory.