When Sun was designing Java, it omitted multiple inheritance
- or more precisely multiple implementation inheritance - on purpose.
Yet multiple inheritance can be useful, particularly when the
potential ancestors of a class have orthogonal concerns. This article
presents a utility class that not only allows multiple inheritance to
be simulated, but also has other far-reaching applications.
Have you ever found yourself wanting to write something similar to:
public class Employee extends Person, Employment {
// detail omitted
}
Here, Person is a concrete class that represents a person,
while Employment is another concrete class that represents the
details of a person who is employed. If you could only put them
together, you would have everything necessary to define and implement
an Employee class. Except in Java - you can't. Inheriting
implementation from more than one superclass - multiple
implementation inheritance - is not a feature of the language. Java
allows a class to have a single superclass and no more.
On the other hand, a class can implement multiple interfaces.
In other words, Java supports multiple interface inheritance. Suppose
the PersonLike interface is:
public interface PersonLike {
String getName();
int getAge();
}
and the EmployeeLike interface is:
public interface EmployeeLike {
float getSalary();
java.util.Date getHireDate();
}
This is shown in Figure 1.
If Person implements the Person-Like interface, and
Employment implements an EmployeeLike interface, it's perfectly
acceptable to write:
public class Employee implements PersonLike, EmployeeLike {
// detail omitted
}
Here there is no explicit superclass. Since we are allowed to
specify at most one superclass, we could also write:
public class Employee extends Person implements PersonLike, EmployeeLike {
// detail omitted
}
We would need to write the implementation of EmployeeLike,
but the implementation of PersonLike is taken care of through the
Person superclass. Alternatively we might write:
public class Employee extends Employment implements PersonLike, EmployeeLike{
// detail omitted
}
This is the opposite situation: the EmployeeLike interface is
taken care of through the Employment superclass, but we do need to
write an implementation for PersonLike.
Java does not support multiple implementation inheritance,
but does support multiple interface inheritance. When you read or
overhear someone remark that Java does not support multiple
inheritance, what is actually meant is that it does not support
multiple implementation inheritance.
Stay Adaptable
Suppose then that you have the concrete implementations
Person, which implements the PersonLike interface, and Employment,
which implements the EmployeeLike interface. Although only one can be
selected to be the superclass, it would be useful to somehow exploit
the other implementation.
The easiest way to do this in Java is by applying the
(Object) Adapter pattern. If we make Person the superclass, we can
use Employment using an object adapter held within the employee:
public class Employee extends Person implements PersonLike, EmployeeLike {
private EmployeeLike employment = new
Employment();
public float getSalary() { return
employment.getSalary(); }
public java.util.Date getHireDate() { return employment.getHireDate(); }
}
For each method of EmployeeLike, the employee delegates to
the object adapter. This helps motivate the decision as to whether
Person or Employment should be the superclass; choose the one with
the most methods as the superclass so there will be less manual
delegation code to write when dealing with the other interface.
The Adapter pattern is a fine way to support multiple
interface inheritance while exploiting two concrete implementations.
Indeed, it's more often the case that an anonymous inner class is
used as the object adapter, allowing customization of behavior with
respect to the context (of being embedded within a subclass).
However, writing that delegation code is tedious, especially
if both interfaces to be implemented have many methods in them. In
many cases, we can get Java to do the delegation to the would-be
superclass(es) automatically.
Enter Dynamic Proxies
Dynamic proxies were introduced into Java in J2SE v1.3. Part
of the java.lang.reflect package, they allow Java to synthesize a
class at runtime. The methods supported by this synthesized class are
specified by the interface (or interfaces) that it implements. The
implementation is taken care of through an invocation handler
(java.lang.reflect.InvocationHandler) that is handed an object
representing the method being invoked (java.lang.
reflect.Method). As you can see, dynamic proxies use heavy doses of
the Java Reflection API.
This then is the key to simulating multiple implementation
inheritance within Java. We can write a custom InvocationHandler that
is constructed with a set of classes; these represent the
superclasses of the subclass to be synthesized. The interface(s) of
our subclass will be the union of the interfaces implemented by these
superclasses. Our InvocationHandler will instantiate instances of
these superclasses so that it can delegate to them. We then arrange
it so that the invocation handler, on being given a method to be
invoked, will reflectively invoke the method on the appropriate
superclass object instance. (There must be one; remember the
subclass's interface is derived from the superclass's, so at least
one superclass must be able to handle the method invocation.)
To make things simple, we can make our InvocationHandler
implementation also return the proxy. In other words, the invocation
handler can act as a factory, able to return instance(s) of the
synthesized subclass that will delegate to the superclass instances.
We call our invocation handler implementation DelegatorFactory for
this reason:
// imports omitted
public final class DelegatorFactory
implements InvocationHandler {
public Object getObject() {
return Proxy.newProxyInstance(
this.getClass().getClassLoader(),
getSupportedInterfaces(),
this);
}
}
// code omitted
}
The supported interfaces of the resultant object are derived
from the superclasses provided in the DelegatorFactory's constructor:
// imports omitted
public final class DelegatorFactory implements InvocationHandler {
public DelegatorFactory(final Class[]
ancestors) {
// implementation omitted
}
// code omitted
}
There is more to DelegatorFactory as we shall soon see, but
we now have enough to simulate multiple implementation inheritance.
Going back to the question first posed, instead of:
public class Employee extends Person, Employment {
// detail omitted
}
followed (presumably) by:
Employee employee = new Employee();
We can instead write:
Object employee =
new DelgatorFactory(
new Class[] {
Person.class,
Employee.class
}
).getObject();
Although the syntax is somewhat different, the same essential
information is being provided. That is, the concrete implementations
are provided in Person and in Employment. This object will use the
implementation of Person if invoked as a PersonLike, and the
implementation of Employment if invoked as an EmployeeLike:
((PersonLike)employee).getAge();
((EmployableLike)employee).getHireDate();
How Convenient
In the above example, the casts are necessary because the
getObject() method of DelegatorFactory can only return a reference of
type java.lang.Object. But the clunkiness arises because our original aim of
defining the Employee class with two concrete superclasses actually
does something else as well:
public class Employee extends Person, Employment {
// detail omitted
}
Not only does this indicate that the implementation of
Employee should be based on that of its superclasses, it also defines
Employee as a type. In other words, it's then possible to write:
Employee employee;
What is missing in our dynamic proxy solution is this
definition of type. Let's first do that in the usual way. As shown in
Figure 2, we don't need to use a class though; an interface is
sufficient.
As code, this is simply:
public interface Employee extends PersonLike, EmployeeLike { }
There is no detail omitted here; this is our complete
definition. Note that Employee is now an interface and not a class.
The following will not work, however:
Employee employee =
(Employee)
new DelegatorFactory(
new Class[] {
Person.class,
Employment.class
}
).getObject();
This is because the only interfaces implemented by the
dynamic proxy returned by getObject() are PersonLike and
EmployableLike. No matter that logically the Employee interface does
not require any additional implementation from our dynamically
created object; Employee is not an interface that we can cast to.
However, DelegatorFactory does provide an alternative constructor:
Employee employee =
(Employee)
new DelegatorFactory(
new Class[] {
Person.class,
Employment.class
},
Employee.class
).getObject();
Note the new second argument (Employee.class) to the
constructor. Casting the object returned from getObject() to Employee
will now work. Behind the scenes, the Delegator-
Factory simply adds this interface to the set of those to be
implemented by the dynamic proxy. Note that Delegator
Factory takes this interface object on trust: there is no validation
that the interface doesn't introduce any new methods that are not
already present in the interfaces of the superclasses.
Initializing the Superclasses
In "regular" Java, if a superclass does not provide a no-arg
constructor, it's necessary for the subclass to correctly initialize
the superclass using constructor chaining. Normally this is done by
including the superclass's constructor's argument(s) in the
subclass's constructor's argument(s), and then passing them up the
class hierarchy using super().
The facilities shown in Delegator-Factory thus far do not support this. The DelegatorFactory is given a list of superclasses, and then instantiates an instance of each (to delegate to) using java.lang.Class.newInstance(). This requires a
public no-arg constructor to exist.
If the would-be superclass does not offer a public no-arg
constructor, the DelegatorFactory should be instantiated using a
different constructor that takes preinstantiated superclass instances:
Person person = new Person("joe", 28);
Employment employment =
new Employment(someCalendar.getTime(),
30000);
Employee employee =
(Employee)
new DelegatorFactory(
new Object[] {
person, employment
},
Employee.class
).getObject();
If the would-be superclass does not have a public
constructor, or is abstract, a custom subclass (probably an anonymous
inner class) should be instantiated and used instead.
Dealing with Diamonds
Typically, multiple implementation inheritance is used when
the superclasses have orthogonal concerns. Certainly this is the case
with PersonLike and EmployeeLike, and each method is unambiguous as
to which ancestor it relates to.
However, sometimes there may be a common super-interface in
the interfaces implemented by the "superclasses." For example,
suppose we have the concrete class, Car, which implements Driveable,
the Boat class, which implements Sailable, and both Driveable and
Sailable extend from Steerable. Since we want to use both Car and
Boat to define a new subclass, we will also introduce a convenience
interface, AmphibiousCar (see Figure 3).
The steer() method of Steerable is used to alter the bearing
(0 to 359 degrees) of the steerable object. The getBearing() method,
of course, should return this bearing.
For simplicity, the drive() method of Driveable and the
sail() method of Sailable return a suitable string indicating the
current bearing. Invoking drive() might return a string such as:
driving at bearing 30 degrees.
From what we currently know, we would create an amphibious
car object using:
AmphibiousCar ac =
(AmphibiousCar)
new DelegatorFactory(
Class[] {
Car.class, Boat.class
}).getObject();
What happens if we invoke the steer() method on our new
amphibious car ac? Should the invocation handler delegate to the Car
superclass object or the Boat? The default behavior is to delegate to
the first matching object. Hence, we will get:
ac.steer(30);
System.out.println(ac.drive());
// prints "driving at bearing 30 degrees"
System.out.println(ac.sail());
// prints "sailing at bearing 0 degrees"
The Boat superclass component of our class never knew that
the bearing had changed.
It's this kind of problem that persuaded the Java language
designers to exclude multiple implementation inheritance. This is too
large an area to cover in this article, but what we have here is an
example of part of the so-called "diamond" problem, where there is a
common ancestor. You can see the diamond in the interfaces:
Steerable, Driveable, Sailable, and Amphibious-Car.
The DelegatorFactory utility deals with the diamond problem
by allowing you to specify the invocation behavior to the delegate
superclasses as a pluggable strategy (an example of the Strategy
pattern). The strategy is defined by the InvocationStrategy
interface. The default strategy (InvokeFirstOnlyStrategy) is to
invoke the first ancestor superclass that can handle the method.
However, in the case of the diamond, what is required is that both
ancestors need to handle the method. The InvokeAllStrategy handles
this. If the method being invoked has a nonvoid return type, the
return value from the first ancestor is returned. The two strategies
are shown in Figure 4.
The invocation strategy can either be set after the
DelegatorFactory has been instantiated, or can be set through (yet
another) overloaded constructor. Hence our amphibious car should be
created using:
AmphibiousCar ac =
(AmphibiousCar)
new DelegatorFactory(
Class[] {
Car.class, Boat.class
},
new InvokeAllStrategy()
).getObject();
This time, we get:
ac.steer(30);
System.out.println(ac.drive());
// prints "driving at bearing 30 degrees"
System.out.println(ac.sail());
// prints "sailing at bearing 30 degrees"
The InvokeFirstOnlyStrategy and InvokeAllStrategy are not the
only strategies available (indeed we shall see one more shortly);
however, they should work for most situations.
If a custom invocation strategy is required, it can be
written by implementing the InvocationStrategy interface:
public interface InvocationStrategy {
Object invoke(final List ancestors,
final Method method,
final Object[] args)
throws Throwable
}
The ancestors parameter is an immutable list of the object
instances representing the superclass. The method parameter
represents the Method being invoked, and the args parameter contains
the arguments to that Method. A typical invocation strategy would
likely call method.invoke(S) somewhere within its implementation,
with the first argument (the object upon which to invoke the method)
being one of the ancestors.
We shall look at some applications of custom invocation
strategies shortly. For now, though, an adaptation of
InvokeAllStrategy might be to return the average return value of all
ancestors, not just the return value of the first one.
Implicit Diamonds
In the previous diamond example, the Steerable interface is
explicitly a super-interface of both Driveable and Sailable. What if
the super-interface has not been explicitly factored out, though?
For example, in the original PersonLike and EmployeeLike
example, what if each provided a foo() method, returning a string.
Not imaginative, but never mind. Let's construct our employee and use
an InvokeAllStrategy:
Employee employee = (Employee)
new DelegatorFactory(new Class[]{Person.class, Employment.class},
Employee.class,
new InvokeAllStrategy())
.getObject();
Now let us invoke foo():
employee.foo(); // what will happen?
Should the Person's implementation be called, that of
Employment, or both? Although you might wish that both would be
called (by virtue of our installed strategy), the sad truth is that
only Person's implementation would be called. This is because the
dynamic proxy has no way of knowing which interface to match foo()
to, so it simply matches it to the first interface listed. (It's a
java.lang.reflect.Method that is passed to the DelegatorFactory, not
the string literal "foo()". Methods are associated with a specific
declaring class/interface.) In terms of the DelegatorFactory's
implementation, this means the first superclass listed in its
constructor.
Note also that the compile time type does not matter. Neither
of the following will change the outcome:
((PersonLike)employee).foo();
((EmployeeLike)employee).foo();
In fact, it would be possible to modify DelegatorFactory to
make Invoke-AllStrategy effective in this case, but that would involve parsing on
the Method.getName() rather than the method. However, this has
deliberately not been done. We'd rather you factored out the
super-interface and made the diamond explicit. In the above example,
add a FooLike (or Fooable) interface and make both PersonLike and
EmployLike extend from it.
Other Applications
The issue raised by diamonds (implicit or otherwise) is that
of how to deal with more than one implementation of a given method
within an interface. However, it's interesting to turn this on its
head.
In aircraft and other safety-critical environments, it's
common to implement subsystems in triplicate. For example, there may
be three different navigational systems, possibly with each
implemented by different subcontractors. Each of these would be able
to respond to the request, "Where is the location of the aircraft?"
Other systems within the aircraft interact with the
navigational subsystem through a broker. This accepts the request on
behalf of the navigational subsystem, and then forwards the request
onto each implementation. Assuming there are no bugs in any of those
implementations, they should all respond with the same data (within
some delta of acceptable variance).
If there is a bug in one of the implementations, it may
produce a response that differs wildly from the other two
implementations. In this case, the broker disregards that response
completely and uses the responses of the other implementations that
agree with each other.
The design of DelegatorFactory and its pluggable invocation
strategies make it easy to implement such a broker. Imagine a
Calculator interface that defines a single method add(int, int):int.
We can then have three implementations of this interface, as shown in
Figure 5.
The FastCalculator uses regular integer arithmetic. The OneByOne-
Calculator rather long-windedly performs its arithmetic by
incrementing the first operand one-by-one in a loop. Both of these
implementations are correct, just different. The final
BrokenCalculator is just that; it actually performs a subtraction,
not an addition.
The InvokeSafelyStrategy invocation strategy requires at
least three ancestors that implement each method invoked. It will
invoke the method on all ancestors, and then look to see that there
is precisely one response that is most popular. Here is how to create
a safe calculator that will ignore the incorrect implementation
within the BrokenCalculator:
DelegatorFactory dfInvokeSafely =
new DelegatorFactory(
new Class[] {
BrokenCalculator.class,
OneByOneCalculator.class,
FastCalculator.class
},
Calculator.class,
new InvokeSafelyStrategy()
);
Calculator safeCalculator =
(Calculator)dfInvokeSafely.getObject();
assertEquals(7, safeCalculator.add(3,4));
Note that the InvokeSafelyStrategy is not all that
intelligent. It stores the return values from each ancestor within a
HashSet, so it relies on an accurate implementation of equals() and
hashCode(). If the actual return type were a float (wrapped within a
Float object), a more sophisticated invocation strategy would most
likely be required. In general, this strategy will work only with
well-defined value objects that can intrinsically deal with any
rounding and other such errors.
You could easily adapt or refine the InvokeSafelyStrategy
into further strategies. For example:
A parameterized version of InvokeSafelyStrategy could be used
to deal with floats and other return types that would have rounding
issues.
A background strategy might perform each invocation within a
separate thread. Any invocation that had not responded within a
certain timeout would be discarded.
A high-performance system, on the other hand, might use a
strategy that again uses a backgrounding strategy but returns the
result of the first one to finish, killing off the rest.
A logging strategy might perform some logging and then
forward the invocation (typically to a single delegate).
A caching strategy would check its cache with respect to the
input parameter, and only if the result is unknown would it invoke
the delegate (caching the subsequent result).
A listener/broadcast strategy could represent a collection of
listener objects; notifying all listeners of an event would require
notifying only the broadcaster, which would then iterate over all
listener objects as required.
Moreover, there is nothing to prevent multiple invocations
from being chained together, (that is, the Decorator pattern).
Alternatively, we could imagine a composite strategy (the Composite
pattern) that combines a set of strategies together. Either the
invocation chain (decorator) or the set of leaf strategies
(composite) could be changed at runtime, meaning that we can change
the behavior and responsibilities of the object dynamically. This is
a fundamentally different paradigm from conventional Java with its
static typing. Normally, it's the type/class of the object that
determines its behavior, something that cannot be changed once the
object is instantiated. Here, though, we have ended up configuring
the behavior of objects on an instance-by-instance basis: so-called
instance-based programming. In effect, the whole notion of type
becomes much less important.
There are echoes here too of aspect-oriented programming.
Most aspect-oriented programming uses compile-time techniques (the
term used is "weaving") to add in behavior to classes. The classic
example of aspect-oriented programming is to add logging within all
method calls. You can easily see, though, that these same features
can be incorporated dynamically using invocation strategies; the
decorator/composite invocation strategies would allow an arbitrary
set of aspects to be added to a class. The difference though is that
now the aspects are applied at runtime (and hence can be changed
without recompile and redeployment).
Conclusion
The DelegatorFactory is simple to use, supporting classic
mix-in (orthogonal) multiple-implementation inheritance
"out-of-the-box" and - with its pluggable invocation strategy design
- allows diamond hierarchies to be easily supported. Moreover, the
design also lends itself to other quite unrelated problem spaces; for
example, creating safe systems was explored. Taken to its logical
conclusion, the approach supports both instance-based programming and
aspect-oriented programming.
Of course, what makes DelegatorFactory work is Java's support
for dynamic proxies, and that in turn requires that the ancestor
superclasses implement interfaces. This approach won't work for
class-based designs (JDOM is an example that comes to mind). But
arguably class-based designs should be used only for value objects
that should be final anyway. Those situations where multiple
inheritance is desired are more likely to occur when working with
reference objects.
One particular case deliberately not supported by
DelegatorFactory is when there is a so-called implicit diamond. The
solution though is to pull out the methods that appear in both
interfaces, and move them into a new super-interface. Then, make sure
you use InvokeAllStrategy rather than the default
InvokeFirstOnlyStrategy.
Of course, using a dynamic proxy object will be slower than a
hand-crafted solution, principally because reflection is used.
However, the difference may not be noticeable in practice. In recent
releases of Java, Sun has put much effort in speeding up reflective
invocation; as of JDK 1.4.1, it may well be that regular invocation
is only twice as fast as reflective invocation (previously this
figure was something like 40 times faster).
Using DelegatorFactory
The DelegatorFactory utility class and supporting classes
described here can be downloaded from below , and are compilable using Ant (v1.5.1 was used to create the build file). A JUnit-based test
harness is also provided; JUnit v3.8.1 is required. The motivating
examples in this article are based on the JUnit tests, so they should
be easy enough to follow.
To run the tests with JUnit's text-based test runner, use:
ant test
Alternatively, you can use JUnit's test runner by running directly:
ant rebuild
java -classpath
dist/halware-util-dynamic-bin.jar;dist/halware-util-dynamic-bin-test.jar
com.halware.util.dynamic.test.AllTests gui
(The GUI test runner is not the default since JUnit's
classloaders do not understand the Class-Path manifest attribute.)
I hope you find DelegatorFactory useful. It has been
distributed under the GNU Lesser Public License, so you are free to
embed it within your own software as required.
Acknowledgments
The inspiration for this article came from a session
presented by Benedict Heal at the Object Technology Conference
OT2002, run by the British Computer Society and the IEE. See
www.ot2002.org/programme.html. Thanks, Benedict, for your further
review comments on the draft of this article.
The UML class diagrams were created directly from the Java
source code using Together ControlCenter, see www.borland.com.
About The Author
Dan Haywood has worked on numerous software development projects for
more than 13 years. He's an independent consultant, trainer, and
technical writer, having previously been a consultant for Sybase
Professional Services and Accenture. His books include Better
Software Faster, addressing the effective use and customization of
Together Control Center, and the EJB chapters for SAMS Teach Yourself
J2EE in 21 Days. Dan's latest interest also uses Java reflection
heavily, namely Naked Objects (www.nakedobjects.org).
dan@haywood-associates.co.uk