Sunday, July 26, 2015

Economy of Means: On the Elimination of Inheritance (6/6)


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:
  1. an instance of B has the signature (interface) of A: it offers all public methods of A and can be put in a collection of objects of type A,
  2. a method that belongs to A is written only once and then provided by all descendants, thus allowing code reuse in the whole hierarchy,
  3. a method declared as abstract in A can be implemented anywhere lower in the class hierarchy, for instance in B,
  4. B can call any non private method of A through keyword super,
  5. a constructor of B can transmit parameters to a constructor of A,
  6. class B can redefine (override) methods of A,
  7. if code in A calls one of its method which is overridden in B, then it is the version in B which gets executed (this language feature is called dynamic method dispatch),
  8. code in B can call the instance-protected methods of A on the same instance (accessible via this or super),
  9. code in B can also call the class-protected methods of any other instance of A (accessible via a method or constructor parameter of type A).
Basic inheritance
A working design which covers points 1 through 3 involves:
  • an interface IA with all the visible (both abstract and concrete) methods of A,
  • a concrete class A' with the code of all concrete methods of A,
  • a class B' with the code of B.
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 (say superClass),
  • 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 type IA,
  • this parameter is kept in a new field, say actualThis,
  • all references to this within the code of A' are re-routed via this.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 class B is introduced,
  • class B implements interface B',
  • 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 class A,
  • interface IA extends IAP and adds all the protected methods of class A,
  • interface IBP publishes the externally visible (public and package internal) methods of class B.
  • a factory creates instances of A' and B' for an external user, and hides their actual types under the public interfaces IAP and IBP.


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:

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:
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

3 comments: