Multiple inheritance

Why multiple inheritance?

When modeling a domain, you often want to express more than one "kind-of" relationship for an object. For example:

It would be nice to express these relationships directly in your design by having a class inherit from more than one superclass.

As in a single-inheritance context, a class inherits all of its superlasses' methods and instance variables.

However, since there is more than one superclass, problems with ambiguity arise:

  1. A class C may inherit from two superclasses C1 and C2 that both define a method for a message M. This raises the question: when M is received by C, should C1's method for M be invoked, or should C2's method for M be invoked?
  2. A class C may inherit from two superclasses C1 and C2 that both define an instance variable V. This raises the question: Should two copies of V be inherited, or one?

There are several solutions to these problems...

Duplicate method solutions

Duplicate instance variable solutions

Recall our shape hierarchy. Suppose there were some instance variables in Rectangle and Rhombus:

class Shape { Float area() { ... } }

class Rectangle sublasses Shape {
    Float area() { ... }
    Point topLeft;
    Point bottomRight;
}

class Rhombus subclasses Shape {
    Float area() { ... }
    Point topLeft;
    Point topRight;
    Point bottomRight;
}

class Square subclasses Rectangle, Rhombus {}

Should Square inherit one or two copies of topLeft and bottomRight? This is the duplicate instance variable problem.

With duplicate instance variables, a language designers have taken several approaches:

C++ uses a hybrid of the above approaches. In C++, inheritance links come in two kinds: regular, and virtual. For regular inheritance links, C++ uses "always duplicate"; for virtually inherited base classes, C++ uses the "merge if originally from same declaration" policy.

I am not familiar with any languages that use purely the "merge if originally from same declaration" policy.

Duplicate field solutions: Are we done?

Actually, none of the above approaches to field duplication seems entirely satisfactory. Even the "merge if originally from same declaration" requires the programmer to plan ahead and factor classes in a particular way --- for example, the user had to factor out the Quadrilateral class ahead of time. It would be better if the implementor of Square had some way of saying which features of superclasses it wanted to merge, and which it wanted to duplicate.

Truthfully, the problem of doing multiple inheritance "right" is still an open problem in language design. However, relatively little work is being done on this subject directly, because the question of multiple-inheritance duplication policies is a hairy language-lawyerly detail whose correct answer probably depends on the empirical questions of what programmers need in large, real-world code bases.

On the other hand, some language designers would claim that if a problem has no "obviously right" answer, then the problem needs to be reframed. There is considerable research these days on novel behavior-composition mechanisms that go beyond inheritance. Perhaps one of these mechanisms would yield an "obviously right" answer that inheritance alone does not make obvious.

Note: Multiple inheritance vs. multiple subtyping

Recall that we said that inheritance and subtyping are distinct --- inheritance concerns implementations, and subtyping concerns interfaces.

Java prohibits multiple inheritance of implementation. However, you will sometimes hear that Java supports "multiple inheritance of interface". Of course, given our terminology, this is an oxymoron, but what Java does support is multiple by-name subtyping --- a class can implement multiple interfaces, but it can only extend one superclass.

It turns out that, in practice, you can get much of the flexibility of multiple inheritance by using multiple subtyping.

Returning to our shape hierarchy, one could use Java interfaces and implementations:

interface IShape { Float area(); }
interface IRectangle extends IShape { ... }
interface IRhombus extends IShape { ... }
interface ISquare extends IRhombus, IRectangle { ... }

abstract class Shape implements IShape {}
class Rectangle
    extends Shape
    implements IRectangle {
    Float area() { ... }
}
class Rhombus
    extends Shape
    implements IRectangle {
    Float area() { ... }
}
class Square
    extends Rhombus
    implements ISquare {
}

Notice that Square still can't inherit and reuse code from both Rectangle and Rhombus.

However, the ISquare interface subtypes both the IRectangle interface and the IRhombus interface. Therefore, provided our variables are declared to use only the interface types and not the class types, we do get to use a ISquare wherever a IRectangle would be allowed, and wherever a IRhombus would be allowed.