This is related to the Diamond Problem. Either the language that offers multiple inheritance has explicit support to avoid the related problems, or your best bet is to avoid (multiple) inheritance.
Let me recapitulate the problem in pseudocode:
class ClassA { X() { ... } }
class ClassB extends ClassA { X() { ClassA.X(); ... } }
class ClassC extends ClassA { X() { ClassA.X(); ... } }
class ClassD extends ClassB, ClassC {
X() { ClassB.X(); ClassC.X(); ... }
}
In this design where ClassB and ClassC also call their super method in ClassA, a call to ClassD.X()
will end up calling ClassA.X()
twice. This is the depth-first resolution approach to multiple inheritance as used in C++ and may or may not be what you want.
Other languages try to order (“linearize”) the base classes so that we can simply call the next base class, without knowing in advance which class that will be. Python is one such language:
class ClassA(object):
def x(self):
...
class ClassB(ClassA):
def x(self):
super().x(); ...
class ClassC(ClassA):
def x(self):
super().x(); ...
class ClassD(ClassB, ClassC):
def x(self):
super().x(); ...
For ClassD, the C3 method resolution order (MRO) algorithm will order the classes as ClassD, ClassB, ClassC, ClassA
and searches the classes for methods left-to-right. The super().x()
call does not call into the base class, but continues the search in the MRO for that object. So in this case, the super-call from the ClassB.x()
method will actually be resolved to ClassC.x()
. There are limitations to this approach:
- This requires explicit support from the language's object model. It is not a feature that can be retro-fit later.
- The resulting MRO can be unintuitive, because it doesn't necessarily dispatch to the base class, but could dispatch to seemingly unrelated classes.
- All classes in a multiple inheritance hierarchy must cooperate. Everyone must forward the
super().x()
calls correctly, or this will break. If different classes require different arguments, passing the arguments can be tricky (this is frequently an issue with Python's __init__()
method that serves as a constructor).
Because of these problems, very few languages support real multiple inheritance (most notably Python, C++, and Perl). Even in these languages, multiple inheritance is generally discouraged. Other languages use traits or mixins to provide some aspects of multiple inheritance, without having to define a complicated method resolution order.
In a scenario where you want to dynamically add functionality to some object, the decorator pattern could be more appropriate. It only needs interface inheritance, and completely avoids the problems of class inheritance.
- Define an interface like
interface CanDoX { X(); }
- Provide a base implementation, e.g.
class ClassA implements CanDoX { ... }
Implement various decorators that implement CanDoX
and take a CanDoX
instance in their constructor:
class DecorateXWithB implements CanDoX {
private CanDoX base;
X() { base.X(); ... }
}
You can now assemble a pipeline of decorators in exactly the order you want:
new DecorateXWithD(
new DecorateXWithC(
new DecorateXWithB(
new ClassA(...))))