6.10 Multiple Inheritance
Multiple inheritance occurs when a derived class inherits from more than one base class. Previous examples used only single inheritance, where each derived class inherited from exactly one base class. Multiple inheritance has an intuitive appeal because it allows the behavior of several base classes to be combined in a direct and seemingly obvious way. However, multiple inheritance is not supported in all object-oriented languages. Java, for example, does not provide multiple inheritance. The controversy surrounding multiple inheritance stems from the subtle problems that can arise when it is used, such as the question of what the behavior of a derived class should be if it inherits identical methods (i.e., methods with the same signature) from more than one of its base classes that have different code.
In this section, two situations are described where multiple inheritance is useful and straightforward: combining orthogonal (independent) base classes, and combining an abstract interface with a concrete implementation. The former facilitates improved reuse by allowing combinations of existing base classes, while the latter creates additional flexibility and enhances polymorphism.
Combining Orthogonal Base Classes
If two base classes have no overlapping methods or data they are said to be independent of, or orthogonal to, each other. Orthogonol in this sense means that the two classes operate in different dimensions and do not interfere with each other in any way. Such classes may be inherited by the same derived class with no difficulty.
Classes can be found or deliberately constructed to represent properties that are, or are likely to be, orthogonal to other classes. Such classes can then be easily inherited together with other classes to combine their properties. The term "mixin classes" is sometimes used to describe these classes, because it is convenient to mix in their properties with other classes. An example of a mixin class is the Named class shown below, which represents the property that an entity has a character string "name." A Named object must be constructed with a name, its name can be queried (GetName), changed (SetName), and tested against another name (IsNamed).
Objects that are required to have aa name can acquire this property by inheriting from the Named class in addition to any other class from which it might inherit. For example, to create a NamedNumber - an entity that has all of the capability of a Number and, in addition, has a character string name, can be easily defined using multiple inheritance as shown below.
Notice that the NamedNumber inherits publicly from both the Number class and the Named class. Also notice that the constructor of the NamedNumber must pass the appropriate constructor arguments to both of its base classes. Since the NamedNumber has no private data of its own, the body of its own constructor has no code.
The combined behavior of the NamedBehavior class can be seen in the following example code, which declares and operates on a NamedNumber.
NamedNumber time(0, "Elapsed Time"); ... time.Next(); // method inherited from Number class if (time.IsNamed("Elapsed Time") )... // method inherited from Named class ... cout << time.GetName() << " is " << time.Value() << endl;
In this code, the NamedNumber object responds to methods that it inherits from the Number base class (Next, Value) as well as methods that it inherits from the Named base class (IsNamed, GetName).
The Named class may be used in many contexts. Other entities possessing names are Frames, Canvases, and Buttons. Rather than including their name property independently in each of these classes, the developer can have each class use multiple inheritance to acquire it from the Named class. In addition to being a more efficient programming effort, because of reuse, the Named class provides a single, uniform set of methods. Thus, it is easier to remember that the name of an object can be accessed via a method GetName and not getName in the Frame class and NameIs in the Button class.
Combining Interfaces and Implementations
Multiple inheritance can be used to enhance separation. Separation is one of the most basic principles of good software engineering and object-oriented design, and may be observed in the structure of a class, where the interface of the class is separated from its implementation. It is also at work in polymorphism because the accessing class is separated from knowledge of the exact type of the object being manipulated; the separation in this case is achieved by the base class. While a base class used in a polymorphic way does provide a degree of separation, the accessing class is still restricted to using only classes derived from this specific base class.
The limits on separation achieved by a base class can be seen in the Clock class, the definition of which is reviewed below. Objects of this class can form an association with any object derived from DisplayableNumber. Through polymorphism, the Clock object does not have to be aware of - is separated from the knowledge of - the exact type of the object with which it forms the association.
While the Clock class is useful, its reuse potential is limited by its dependence on the DisplayableNumber base class, which prevents use of the Clock in associations with objects requiring the services of a Clock object but which themselves are not DisplayableNumbers. An example of such a class is an Animation class: an Animation object has a series of images that it will display, and to achieve the animations effect, the images must be displayed a given rate. Driving the animation via a Clock object is the obvious answer; at the end of each time interval the Clock requests that the animation show its next image. (The outline of the Animation class is shown below.) However, the Clock object cannot form an association with an Animation object, only with DisplayableNumber objects. It is clearly inappropriate to derive Animation from DisplayableNumber. Another alternative is to form a new Clock class, AnimationClock, that inherits from Clock and can form an association with an Animation object. This approach, though, has the drawback that the AnimationClock inherits unwanted data and operations (i.e., those that refer to a DisplayableNumber).
The increased separation that can be achieved via multiple inheritance is made clear by a close examination of the Clock class. The Clock class depends on very little: it only uses a Next and a Show method. These two methods are defined in the subclasses of DisplayableNumber, but they are also methods present in the interface of the Animation class. What is needed is a technique for generalizing the common properties of the DisplayableNumber and the Animation class. Inheritance is a technique based on generalization, and since multiple base classes are involved, multiple inheritance is a possible solution to the problem.
The desired separation is achieved by combining, through multiple inheritance, a pure abstract base class with a class that provides the concrete implementation of the abstract methods. Recall that the term abstract class refers to a base class that has at least one pure virtual method. A pure abstract class is one in which all of the methods in the class are pure virtual methods. The pure abstract base class needed in the Clock example is shown below.
The Sequenced class captures the similarity between the DisplayableNumber classes and the Animation class. The pure virtual methods allow these similarities to be defined without requiring any implementation being defined in the Sequenced class itself. The responsibility to implement the pure virtual methods is assumed by the classes derived from the Sequenced class.
The Clock class can now be redefined as shown below to depend on the Sequenced class and not on the DisplayableNumber class. This change improves the reusability of the Clock class because it allows a Clock object to form an association with a wider group of objects, any object that inherits from Sequenced. This change also improves the clarity of the Clock class because it expresses more directly and simply the expectation that a Clock object has of the object with which it forms an association.
The Sequenced class may be combined with the DisplayableNumber hierarchy in one of three ways:
The approach of defining new classes using multiple inheritance is illustrated in the figure below defining the TimedNumber and the TimedAnimation classes. The TimedNumber class and the TimedAnimmation class are named to reflect the intention of using them in association with the revised Clock class.
A critical aspect of the the definition of the TimedNumber and TimedAnimation classes is the way in which they satisfy the responsibility imposed by the Sequenced class. The pure virtual methods in the Sequenced class impose on their derived classes the responsibility to implement a Next and a Show method. The TimedNumber satisfies this responsibility by inheritance from the Number class. The same is true for the TimedAnimation class.
Associations of a Clock with a TimedNumber object and a TimedAnimation object may now be created. The associations are created by the following code:
Clock slowTimer("Slow", 1000); Clock fastTimer("Fast", 50); ... TimedNumber count(0); TimedAnimation *movie = new TimedAnimation(...); ... slowTimer.ConnectTo( (Sequenced&)count ); fastTimer.ConnectTo( (Sequenced*)movie );
The essential element in this code is the ability to type cast TimedNumber and TimedAnimation objects as objects of type Sequenced. This type cast is valid because TimedNumber and TimedAnimation inherit from the Sequenced class.
Finally, the introduction of the Sequenced class allows the Clock class to be used in association with other classes that might be defined in the future. For example, a Sampler class that periodically reports the status of some resource, like how much a file has been transferred over a network or how much of a document remains to be printed might be defined. Through multiple inheritance and the Sequenced class, no changes need to be made to the Clock class in order for a Sampler object to be driven by a Clock object.