ADDING DELEGATION TO JAVA
v1.6 Fri Mar 27 18:26:44 MET 1998
Adding delegation support for Java is a recurrent discussion on the
technical mailing-lists. While it is hard to predict if it will eventually
make it into the language, many proposals have already been advanced. Here
is my contribution to this debate.
CONSTRAINTS
What follows has been greatly inspired by the process used by the ISO C++
Committee to decide the inclusion (or not) of a new feature in the language.
In a few words, an extension should
-
Add functionalities that cannot be gained with the current status of the
language
-
Be easy to explain
-
Be easy to implement
-
Not break any existing code (use a new keyword as a last resort only)
I suggest you refer to #[1] for more information on
this subject.
RATIONALE
Sometimes, I find myself simulating multiple inheritance of implementation
by mixing simple inheritance and delegation. Consider the classic example
of displaying a grapher. I have an "abstract" representation of a node
(class Node) and I want to display it. For this, I have a class Graphic.
My class will naturally be called NodeGraphic and will, at least in C++,
inherit both from Node and from Graphic. And it does make sense : a NodeGraphic
is both a Node and a Graphic.
In Java, it's a bit trickier :
-
You have to choose what will be inherited and what will be delegated
-
And your object still won't "naturally" (not without some efforts) be both
a Node and a Graphic : you won't be able to apply the substitution principle
to it.
public class Node {
public setName() {}
}
public class Graphic {
public setX(int x) {}
public setLabel(String name) {}
}
public class NodeGraphic extends Node {
// delegation takes place here :
public setX(int x) { _graphic.setX(x); }
public setLabel(String name) { _graphic.setLabel(name);
}
Graphic _graphic;
}
As you can see, you end up writing a lot of dumb code, which is error prone
(illustrated below) and on top of this, you can't pass a NodeGraphic to
a function expecting a Graphic (you will have to resort to interfaces to
achieve this, but it won't help if the author of the package Graphic
belongs to didn't envision this).
Worse : if one day Graphic is improved, say with a function
setBackground(), a simple recompilation won't be enough : you
will have to add by hand the setBackground() function to your
class. And this goes for all new functions, and deprecated ones. This is
beginning to rhyme with nightmare, but it's part of the compromise Java
makes with simplicity.
MY PROPOSAL
I would like to have the following syntax :
public interface IGraphic {
// Defines what a Graphic is
};
public class NodeGraphic extends Node implements IGraphic(_graphic)
{
// ...
IGraphic _graphic;
}
Please note that here, the "I" prefix means "interface" (not to be confused
with CORBA's usual notation where you suffix the implementation with
_i).
With this syntax, you are free to use whatever implementation of the
interface IGraphic suits you best (in the constructor of NodeGraphic
for example). Your class will never know about any concrete implementation
of the interface it delegates to.
Of course, you can delegate to more than one attribute :
public class NodeGraphic extends Node
implements IGraphic(_graphic), IComponent(_component)
{
// ...
IGraphic _graphic;
IComponent _component;
};
ALTERNATIVE SYNTAX
Some people are concerned about the visibility of the delegation. They'd
like the fact that the implementation of an interface is actually delegated
to be invisible from the declaration. Therefore, the following alternative
syntax might be used :
public class NodeGraphic extends Node implements
IGraphic, IComponent {
// ...
IGraphic _graphic implements IGraphic;
IComponent _component implements IComponent;
};
RESOLVING AMBIGUITIES
Static ambiguities
This syntax can cause ambiguities. For example, suppose that both these
interfaces have a public method clash(), the following code will
create an error :
NodeGraphic ng = new NodeGraphic();
ng.clash(); // error : ambiguous
You are then expected to disambiguate this code yourself :
NodeGraphic ng = new NodeGraphic();
ng.IComponent.clash();
My experience (C++) tends to prove that such ambiguities are either rare,
or systematic, and in this case, you should arrange for the class that
causes numerous conflicts to use some different naming scheme.
If ever the class NodeGraphic has its own clash() function,
it is the one chosen. Delegated functions come second in the resolution
of overridden methods. A typical implementation in such a case would be
in class NodeGraphic :
public void clash() {
_graphic.clash();
_component.clash();
// do your own clash stuff if any
}
Dynamic ambiguities
There is a particular case that cannot be caught at compile time and will
be ambiguous at runtime. Since delegated members can be modified dynamically,
one member could first be assigned to a "simple" interface, and later on,
replaced with an "enriched" interface (inheriting from the first interface).
If this enriched interface contains a function that clashes with another
implemented interface, the call becomes impossible to determine :
public interface IBase {
public void f();
}
public interface IDerived extends IBase {
public void g();
};
public interface IOther {
public void g();
};
public class A implements IBase(_base), IOther(_other) {
public IBase _base;
// public for clarity
public IOther _other;
};
public class Base implements IBase {}
public class Derived implements IDerived {}
public class Other implements IOther {}
/* ... */
A a = new A();
a._base = new Base();
a.g(); // ok, not ambiguous
a._base = new Derived();
a.g(); // error, is it the g() from Other or Derived
?
To prevent this, I suggest that only those interfaces mentioned in the
declarative part of the class should be considered when trying to perform
a call. Therefore, in the error case, Derived.g() won't be
considered because IDerived is not mentioned in the implements
clause : it's IBase. Since IBase does not contain
a g() (an error would have been triggered), there is no ambiguity.
Another way to solve this problem would be to throw a runtime exception,
much like what happens when you try to store an object of a wrong type
in an array (why this is not caught at runtime, I don't know). Instead
of an ArrayStoreException, you would have some DelegationAmbiguityException.
VISIBILITY OF THE DELEGATED ATTRIBUTE
The attribute used as delegate can have any visibility (public,
protected or private, although public should
be discouraged by standard OO practice). All the functions of the implemented
interface will automatically be accessible as if they had been declared
public.
PROS AND CONS
With this declaration, you are now free to assign _graphic to whatever
implementation of IGraphic suits you best.
This solution has very attractive properties :
-
Simple to explain
-
Simple to parse
Not much added to the existing syntax
-
No new keyword
-
Doesn't break existing code
-
Simple code generation
Basically, node.setBackground() will be compiled to node._graphic.setBackground(),
and there will be no code for setBackground() in the leaf class.
-
True OO gain
The substitution principle can now apply : a NodeGraphic is
both a Node and a Graphic, since we use the familiar 'implements' keyword.
-
True coding gain
No need to delegate by hand all the operations. A simple recompilation
will bring to your class all the functionalities that were added (or removed)
in your ancestors.
The only drawback I see is that it forces a new interface to be introduced
for a class to be delegated this way, but this might be seen as a good
point (personally, I think the more interfaces, the better). One
might also use interfaces to define subsets of classes that are delegated
in their class.
--
Cedric Beust
THANKS
...to Olivier Dedieu,
Lionel Mallet, Thierry
Kormann and Jean-Michel Leon
for their constructive criticism.
REFERENCES
[1] The design and evolution of C++, Bjarne Stroustrup
ISBN 0-201-54330-3, Addison Wesley