The Truth about Multiple Inheritance

October 2006

Most Eiffel programmers probably don't need much convincing on this month's topic, but it is the victim of misconceptions so widely held as to justify a short clarification.

Multiple inheritance has long been the target of a FUD campaign (seeding Fear, Uncertainty and Doubt) which, one must say, has been pretty effective. For fun I sometimes ask a new audience to raise their hands if they have been successfully convinced that multiple inheritance is "bad" in some sense; whether they are students or engineers, the show of hands is always impressive.

There are, I think, two original reasons. The first is that multiple inheritance is hard to implement efficiently; Eiffel was made possible when (among other things, but this was a key factor) we found a way, back in 85, to implement multiple inheritance so that dynamic binding would take constant time. It was not deemed possible at the time; both Simula and Smalltalk had single inheritance. Already then there were nasty comments about multiple inheritance being messy, the unmistakable sign of bad design etc. We always felt that this was an excuse; if you can't implement something, vilify it. The experience of Eiffel quickly showed that multiple inheritance was useful and that excellent designs could take advantage of it. I should add here that the presence of multiple inheritance in Eiffel had a precise source: extensive experience with Simula 67, which only had single inheritance, had demonstrated a contrario that we couldn't do a decent job of modeling systems -- the prime task of programming in the object-oriented view -- without multiple inheritance.

The next episode was multiple inheritance as present in C++ through -- I think one can say without too much fear of contradiction -- not the most lucid language mechanism ever designed. In particular, multiple inheritance just doesn't go well with the C++ idea, also retained in Java and C#, that a constructor (creation procedure) must start its execution by calling the constructor of its parent. As soon as you require this you are ruling out multiple inheritance, since with two parents or more there's no good reason for executing a particular parent's constructor before the others'; and the situation becomes even more hazy when these parents have a common ancestor -- repeated inheritance, the so called "diamond" structure. Hence complicated rules that probably about twelve people in the world understand, and fewer remember.

That mechanism gave further ammunition to the attack on multiple inheritance, but the arguments are about the C++ design more than about the concept per se. Eiffel's design is not subject to most of these arguments. In particular, Eiffel doesn't force the creation procedures of a class to use those of its parents, as there is no reason for such a rule (each class can initialize its instances the way it wants to); and the fundamental structure of an Eiffel class, a one-to-one mapping between feature names and features -- no overloading -- naturally leads to the renaming mechanism which removes any conflict between identically named parent features. Finally, repeated inheritance is addressed through the sharing/replication rule, based on a very simple criterion in line with this principle: a feature multiply inherited under one name is a single feature; a feature inherited under two different names is replicated. The "select" mechanism removes any remaining ambiguity for dynamic binding. One should add that it's only with the ECMA standard that all the details of the mechanism have been ironed out (see the standard text at http://www.ecma-international.org/publications/standards/Ecma-367.htm), which also introduces the useful complementary notion of non-conforming inheritance, but the essentials have been there from the start.

The new form of the anti-multiple-inheritance prejudice is fueled today by the presence of "interfaces" in Java and C#. The new accepted wisdom is that it's OK to be limited to single inheritance from classes, as long as a class can inherit from several interfaces. But this is just as wrong and restrictive.

An interface is, in Eiffel terms, a class that's completely deferred: it only specifies features, but no implementation for any of them. Note in passing that in the languages mentioned the meaning of "specifies" is much weaker: one of the

benefits of Eiffel's deferred features (features specified but not implemented) and deferred classes is that they can have contracts even without the implementation; a deferred feature may have a precondition and a postcondition, and a deferred class may have an invariant. Without contracts, "specified" simply means having a type signature, which is not much. But there are even worse problems.

Forcing an interface -- as used in multiple inheritance -- to be completely deferred means it can't have any algorithm at all. A typical case of where this rules out useful applications is a class like COMPARABLE, describing objects comparable through a total order relation. It seems at first like a good candidate for an interface: the feature "<" -- alias for `less_than' -- is deferred, to be implemented in descendants describing specific kinds of ordered values (integers, reals, tennis players, whatever). But now you will also want ">". There's only one reasonable definition for a > b once you have "<":

Result := (b < a)

This is an implementation! Same thing for "<=":

Result := (a < b) or (a.is_equal (b))

and ">=". So what you do in Eiffel is to make COMPARABLE deferred with the feature "<" deferred, and the other three effective (non-deferred, i.e. implemented) with the implementations as shown for two of them.

Then any descendant class that needs an ordering property, such as TENNIS_PLAYER, inherits COMPARABLE and implements "<" to describe the particular order relation of interest to you. The effective features -- here, the other three -- follow automatically. End of story. Thanks to multiple inheritance, TENNIS_PLAYER can inherit from anything else it wants to, for example a class PLAYER describing players in any game, or CLUB_MEMBER etc.

With interfaces you cannot do that. Any descendant of COMPARABLE will have to implement all the features. It's code duplication, with all the standard
problems: it's tedious to program; worse yet, it's error prone because there is no way to avoid a mistaken implementation of ">", "<=" or ">=" in some descendant. You don't even have contracts to try to enforce some discipline on such redefinitions in descendants.

The example of COMPARABLE is important because it's typical of a class meant to be used through inheritance; it provides just a facet of functionality -- ordering -- to be combined with many others, so most of its descendants will typically need other parents as well. But it also provides some default implementations for some of the functionality. You can't do this with interfaces.

Here is another example, drawn from the well-known Observer pattern. Never mind that you don't really need this pattern in Eiffel, because you can do better on every count thanks to agents (you can find a paper on this at http://www.inf.ethz.ch/~meyer/publications/events/events.pdf, and the supporting Event Library). Let's just consider the pattern as it is usually described. "Publisher" objects must be instances of classes inheriting from a high-level class PUBLISHER, and similarly for "subscriber" objects with a class SUBSCRIBER. A typical publisher object is a GUI element which, when it detects that some event such as a mouse click has occurred, notifies all the subscribers, of which it keeps a list, here called `subscribers'. This is done through a procedure `publish' involving a loop:

publish
-- Notify all subscribers that event has occurred.
do
from subscribers.start until subscribers.after loop
subscribers.item.update
subscribers.forth
end
end

On the subscriber side, there is a procedure `update', which describes what subscribers of the given type must do in response to such an event. At the level of class SUBSCRIBER, the procedure is deferred; specific descendants implement it, each in the desired way. Then the above code works beautifully thanks to dynamic
binding: it calls the right `update' depending on the particular type of each subscriber object, obtained through `item'. But SUBSCRIBER should not be just an interface; for example it needs a procedure

subscribe (p: PUBLISHER)
-- Subscribe me to `p'.
do
p.subscribers.extend (Current) -- i.e. add me to p's
-- subscribers list
end

which is as effective as they get (and could use a contract, I know, but this is just a sketch with the essentials).

Like the descendants of COMPARABLE in the preceding example, the descendants of SUBSCRIBER are ordinary application classes; along with SUBSCRIBER they need their specific parents. And it's just the same for the descendants of PUBLISHER: many of them will need to inherit from other classes as well. But PUBLISHER, like SUBSCRIBER, cannot be completely deferred; true, it has some deferred features, but also the effective procedure `publish', with its implementation as given above, the heart of the Observer pattern.

Yet another kind of example, familiar to Eiffel programmer, is the "compound" structure such as COMPOUND_FIGURE which inherits from both FIGURE and LIST [FIGURE]. An amusing aspect of this example, which I have often used in lectures, is that people who have been somehow told since childhood that "multiple inheritance is bad" and that it usually signals the even worse vice of "implementation inheritance" initially exclaim: "but the other link should not be inheritance!". Then I ask which one of the two is the improper one, and it turns out half of the audience thinks it's FIGURE and the other half that it's LIST! In fact, both are perfectly legitimate inheritance links. A composite figure is a figure, to which you can do all that one does to figures -- show, hide, scale, move, rotate and the whole nine yards --; and it's a list, to which you can do all that one does to lists: add an element, remove an element, change the order of elements, iterate an operation on all the elements (like `publish' did to `subscribers' above with `update'). Classes like FIGURE and LIST cannot, again, be replaced by interfaces. If you only have single inheritance, you end up duplicating considerable amounts of code.

In Eiffel programming you encounter such examples daily and don't think twice about using multiple inheritance. Interfaces don't do the job.

The notion of interface results, in my opinion, from a serious misunderstanding of object-oriented principles. A key property of object technology is the need for a continuum in the description of systems, from the most abstract view -- requirements- oriented, user-centered -- to the most concrete (at the implementation stage), and everything in between. Deferred classes and inheritance support this process of constant refinement, since they enable you to capture at each level exactly what you know, and leave open what you don't know, with contracts at your disposal to constrain future refinements to the desired properties. Having just interfaces and classes destroys all this, since it causes an all-or-nothing choice: implement either nothing or everything.

It's a very crude view, a move backwards to a time when software developers had to reconcile high-level specifications devoid of practicality and low-level programming languages devoid of abstraction; it's the negation of what we have learned about the need for a seamless development process, and it is amusing but also a bit sad to see such a disqualified concept presented as the latest innovation in software design.

-- Bertrand Meyer