Method call forwarding: a language extension
For a brief moment, allow me to dream that extensions could be easily and instantaneously incorporated into the Java language. It is a dangerous idea, because one never knows how new features will interact with already existing aspects of the language. And it is certain that human beings, as a group, are extremely innovative, too much for their own good. So one cannot easily predict how new constructs will be abused of.
Still, I would take my chances and wish for the following:
- package internal visibility for interfaces methods,
- automatic call forwarding, a new language construct studied extensively in this paper.
Let me show you how the code presented last in the previous section would be simplified:
public interface Component {
public void setBackgroundColor(Color color);
Box computeBox(int x, int y);
void draw(int x, int y, int width, int height, Graphics canvas);
}
public HorizontalLayoutContainer implements Component {
private Background background;
private int padding;
private List<Component> components;
forward to this.background;
public HorizontalLayoutContainer() {
this.background = new Background();
this.padding = 0;
this.components = new ArrayList<Component>();
}
Box computeBox(int x, int y) {
int x = location.getX();
int y = location.getY();
int width = 0;
if (!this.components.isEmpty()) {
width = -this.padding;
}
int height = 0;
List<Box> content = new ArrayList<Box>();
for (Component component: this.components) {
Box box = component.computeBox(x + width, y);
content.add(box);
width = width + box.getWidth + this.padding;
height = Math.max(height, box.getHeight());
}
return new Box(x, y, width, height, this, content);
}
public void setPadding(int padding) {
this.padding = padding;
}
public void add(Component component) {
this.components.add(component);
}
public void remove(Component component) {
this.components.remove(component);
}
}
public Label implements Component {
private Background background;
private String text;
private Font font;
private Color textColor;
forward to this.background;
public Label(Font font) {
this.background = new Background();
this.text = "";
this.font = font;
this.textColor = Color.BLACK;
}
Box computeBox(int x, int y, Graphics canvas) {
FontMetrics metrics = graphics.getFontMetrics(this.font);
int height = metrics.getHeight();
int width = metrics.stringWidth(text);
return new Box(x, y, width, height, this);
}
void draw(int x, int y, int width, int height, Graphics canvas) {
this.background.draw(x, y, width, height, canvas);
canvas.setColor(this.textColor);
canvas.setFont(this.font);
canvas.drawString(this.text, x, y + height);
}
public void setText(String text) {
this.text = text;
}
public void setTextColor(Color color) {
this.textColor = color;
}
}
// all the other classes remain unchanged
Line
forward to this.background;
means that any call to a method which is not directly defined in the class, will be automatically forwarded to instance background
. In practice, for class HorizontalLayoutContainer
this concerns both methods draw
and setBackgroundColor
. Whereas in class Label
, this is only for method setBackgroundColor
. The code of method draw
in class Label
shows how method overriding is simply achieved by standard composition.
Even though keyword
forward to
is only syntactic sugar for what was previously manually achieved, it reduces the cost of modifying the program. Suppose, we wish to add a new method which is shared by all components. All we need to do is declare this new method signature on interface Component
, and implement its code in class Background
. The components themselves are left untouched: there is no need to painstakingly wire the additional call for each of them.
The principal advantage of replacing inheritance by automatic call forwarding is simply this: if the wrong design decision is made, the code is only one step away from a design that uses composition only. This is less costly to unravel than entanglements found in code filled with misuses of class inheritance.
A systematic translation
As we have seen throughout the previous posts on the topic, a combination of class composition, interface inheritance and automatic call forwarding, can often advantageously replace class inheritance. But, is it truly always the case? Isn't there the risk of a dramatic rise in syntax volume and a resulting escalation of maintenance costs? Most importantly, can the exact same API be always presented to the external user? In order to convince you so, I will sketch a code transformation that systematically removes class inheritance. However, for concision sake, I will indulge in a few simplifications:
- all fields are assumed to be private and accessed via methods.
- package internal access modifier can be treated exactly like the public visibility modifier. It will thus not be discussed.
- protected access modifier will not include package internal visibility (this is one of the many subtle differences between Java and C#)
First, let us recapitulate everything that an inheritance relationship of
B
from A
enables:- an instance of
B
has the signature (interface) ofA
: it offers all public methods ofA
and can be put in a collection of objects of typeA
, - a method that belongs to
A
is written only once and then provided by all descendants, thus allowing code reuse in the whole hierarchy, - a method declared as abstract in
A
can be implemented anywhere lower in the class hierarchy, for instance inB
, B
can call any nonprivate
method ofA
through keywordsuper
,- a constructor of
B
can transmit parameters to a constructor ofA
, - class
B
can redefine (override) methods ofA
, - if code in
A
calls one of its method which is overridden inB
, then it is the version inB
which gets executed (this language feature is called dynamic method dispatch), - code in
B
can call the instance-protected methods ofA
on the same instance (accessible viathis
orsuper
), - code in
B
can also call the class-protected methods of any other instance ofA
(accessible via a method or constructor parameter of typeA
).
Basic inheritance
A working design which covers points 1 through 3 involves:
- an interface
IA
with all the visible (both abstract and concrete) methods ofA
, - a concrete class
A'
with the code of all concrete methods ofA
, - a class
B'
with the code ofB
.
If class
A
is not abstract, then A'
is marked as implementing IA
.
Class B'
differs heavily from B
in that inheritance is replaced by composition:- all its constructors create a new instance of
A'
and store this instance in some new field (saysuperClass
), - automatic call forwarding is performed onto
this.superClass
, - lastly it is marked as implementing interface
IA
.
Method override
Point 4 to 6 are easy: methods overridden in
B
are simply defined in B'
and any occurrence of super
in B
is replaced by a reference to field this.superClass
.Dynamic dispatch
Point 7 is rather tricky. Generally speaking, a method call that goes in the opposite direction of the class hierarchy is a sign of a design flaw; especially when the method called is abstract. In this case, the parent class should most probably be split and the strategy pattern applied (as was shown previously). However, this insight is not enough: how should the parent class be split? Indeed, nothing prevents cycles in the call graph between methods of the same class.
Hopefully, we can aim for something simpler. All that is really necessary is for the parent class to hold a reference to its descendant:
- all
A'
constructors expect an additional parameter of typeIA
, - this parameter is kept in a new field, say
actualThis
, - all references to
this
within the code ofA'
are re-routed viathis.actualThis
so that calls reach the correct implementation.
Class
B'
would need to pass itself (this
) when calling any constructor of A'
. Note that passing this
to a constructor is a clear code smell: at least now, the dependence cycle is explicit.Inheritance chain
I am all for flat hierarchies! Still does the previous transformation apply for any arbitrary level of inheritance depth?
Let us suppose that class
C
extends class B
. Class B'
is produced the way class A'
was, and class C'
the way B'
was:- an interface
IB
with the visible signature of classB
is introduced, - class
B
implements interfaceB'
, - its constructors get an additional parameter of type
IB
, which is stored in an additional field (actualThis
), - all occurrences to
this
are replaced by reference to this new field, - etc, etc...
All seems to go seamlessly. But, a detail is missing. Maybe you already spot the flaw? It is already present with an inheritance depth of only 1?
What if the external user of the class hierarchy needs to instantiate class
B'
(or class A'
for that matter) directly? What is she supposed to pass to the actualThis
constructor parameter?
The fix is easy but dull. Every constructor must be doubled: one version expect parameter
actualThis
, the other assigns this
to actualThis
instead.Protected methods
Now we get to the delicate matter of protected methods (points 8 and 9).
In Java, accessibility modifier
protected
really encompasses two distinct aspects: access on the same instance (through this
), or access to a distinct instance (through a method parameter).
I wonder if there are any concrete cases that can demonstrate the necessity of class-protected accessibility. Especially considering the fact that class-private combined with instance-protected might always prove to be a sufficient alternative. If you happen to find any real example, please let me know!
Anyway, if we still wish to keep this aspect, then we must extend Java with allow class-protected methods in interfaces. The design described in the previous section applies. The external user of the class hierarchy never sees class
A'
, but always interface IA
instead. Hence, all method parameters that have type A
in class B
, are declared with interface IA
in class B'
.
On the other hand, if we consider instance-protected accessibility only, then a more elaborate design allows us to completely bypass keyword
protected
:- interface
IAP
contains the public (and package internal) methods of classA
, - interface
IA
extendsIAP
and adds all the protected methods of classA
, - interface
IBP
publishes the externally visible (public and package internal) methods of classB
. - a factory creates instances of
A'
andB'
for an external user, and hides their actual types under the public interfacesIAP
andIBP
.
Expunged of inheritance the language becomes much easier to learn. Many keywords with rather complex semantics become unnecessary:
override
, abstract
, extends
, final
, super
. That is why I would happily trade inheritance for automatic call forwarding. This seems also to be the choice of the Go language designers, albeit presented a bit differently: Go has embedded types.
In any case, various recent programming languages are experimenting horizontal reuse as an alternative way to assemble fragments of behaviors. Here is an incomplete list:
- union type (C, OCAml),
- duck typing (Python, JavaScript),
- traits (Scala, php),
- mixins (Ruby),
- methods and class decorators (Python),
- monkey patching (JavaScript),
- composite oriented programming,
- feature fragments oriented programming,
- Java 8 default method,
- aspects, roles, subjects...
Loops of Zen |
The missing zero symbol
Conventional (crowd) wisdom seems to esteem programming languages which are geared with all the latest fancy gadgets. On the contrary, I believe programming languages should have a central paradigm and a few orthogonal constructs to support this paradigm. A minimalist, yet well-thought, language, does not waste your time evaluating the strengths and weaknesses of each of its numerous primitives. It helps you solve your problems, instead of having you solve its own design issues.
The inheritance vs. composition opposition is a major dilemma of classical object-oriented languages. Even though, most object oriented tutorials put a lot of emphasis on the concept of inheritance, the mantra to prefer composition over inheritance can also be quite often encountered. Even Java's creator, James Gosling, wonders about class inheritance.
I believe (and I am not alone, see this) we can make an even stronger claim: inheritance packs too many aspects at once (see A systematic translation for a detailed discussion). It is extremely rare for all these aspects to be required together. Hence, using inheritance couples classes more than is desirable and leads to monolithic code. Hopefully, it is possible to rephrase most programs without any, or at least very few class hierarchies.
With the help of small concrete examples, I have shown some techniques that allow to advantageously replace the various aspects of inheritance by alternative, more precise, designs:
- simply suppress the hierarchy when it is not necessary, by merging together (Deaf to YAGNI) or splitting (Default behaviour) classes,
- prefer composition to reuse code and avoid copy-paste (DRYing the wrong way, Object happy families)
- propose families of heterogeneous implementations via a common interfaces (A matter of choice, Avoiding override, Object happy families),
- apply the strategy pattern to eliminate abstract base classes which implement the template method pattern (From template method to strategy, Revisiting keyword abstract, Object happy families). This loosens up class coupling, since the dependence will now go purely from son to father rather than both ways.
- replace the code of multiple but shallow descendant classes by pure data. A factory provides creation methods in replacement for the various constructors (Antagonism between class hierarchy and methods call chain, Revisiting keyword abstract),
- get various behaviours out of a single interface by composition of decorators (From inheritance to decorator).
Eliminating inheritance, exposes the hidden, yet true, complexity of a design. It leads to a more focused, more precise, more intelligible code base. With smaller, decoupled, reusable components, the source code becomes more malleable.
Primitive numeral systems were lacking a symbol for zero. In exchange, they had several symbols for large numbers like ten, a hundred, a thousand.
Inheritance and a host of related complex notions (abstract, override, protected, super, final) are exactly like these large numbers: unnecessary nuisance.
Maybe automatic call forwarding will prove to be the missing zero. In any case, together with interface inheritance and composition, it offers a simple yet powerful alternative.
A language with these primitives would be the truly minimalist approach, a real economy of means.
Zhou Bi Suan Jing |