"My own view of inheritance is broad. My colleagues and I have defined and use the eleven categories described below extensively. I find them both theoretically legitimate and practically indispensible."
Model inheritance - "isa" relationships
In OO software, it is not uncommon to extend a base class by having derived classes introduce new "features". A legitimate example might be a class MovingPoint that inherits off of class Point, and adds an attribute called Velocity. Meyer off-handedly comments, "in most applications, of course, Square should inherit Rectangle, not the reverse." But he goes on to maintain that "we cannot dismiss the general category of extension inheritance", because it offers real and practical benefit.
Proper use of inheritance to implement subtying according to the Liskov Substitution Principle can be achieved using inheritance with addition. Adding new member functions to a class intuitively reduces the number of objects it characterizes. For example, adding a speed calling function to a simple phone creates a new phone abstraction, with more functions than the original, and a smaller subset of applicable instances. Because existing operations are left intact, an object of the derived class can still be used where a base class object is expected. However, this also means that software manipulating such objects in terms of their base class interface will not be able to exercise the new member functions added at lower levels. [Coplien, p232]
Using inheritance with addition, we can add new member functions in derived classes but cannot access those functions in terms of their base class interface. This is quite a restrictive model of inheritance and leaves no room for run-time dispatching of the new functions (how the object paradigm derives much of its power). [Coplien]
An example: class Circle has a new feature called "radius" which captures the distinguishing characteristic of instances of Circle (the common distance of all points from the merged center). This deserves the status of an attribute of class Circle, whereas the corresponding notion in class Ellipse (the average of the distances to the two focuses) is probably not significant enough to yield an attribute. [Bertrand Meyer]
The presence of both the restriction and extension variants of inheritance is one of its paradoxes. But Meyer maintains, "I find them both theoretically legitimate and practically indispensible."
If a circle is anything, it is a specialized form of ellipse with the two foci at the same point (the same way a square is a specialized form of reactangle with all four sides equal). When you use the word "specialized" or "particular", as in "a particular kind of" to describe a relationship, you may have a good candidate for inheritance. [Ken Arnold, "Class design for inheritance", Unix Review, Jul 94, p68]
class Base { public: void function1(); void function2(); }; class Derived : public Base { public: void function3(); private: Base::function1; // cancellation (not valid C++) };However, if private derivation is used (effectively cancelling all the public operations of the base class), the base class operations can be made accessible again one by one.
class List { public: void insert(...); int count(); int has(...); }; class Set : private List { public: void insert(...); // redefining insert() List::count; // making count() accessible List::has; // making has() accessible };"Derivation with cancellation may be a hint that an inappropriate inheritance structure has been put in place. In general, problems abound in the area of cancellation, and you should avoid it as a design technique, and even as a design accident, with great care." [Coplien, p239]
It is possible to simulate inheritance with cancellation in C++ using private inheritance. This should normally be avoided, as it increases complexity in the inheritance structure. In any case, it is likely that it is really another type of relationship (has-a) that has been wrongly implemented with inheritance(is-a). [Karlsson, p329]
"A derived class can override virtual member functions called by functions in a private base class, so private inheritance works better than containment for two classes collaborating for reuse. A base class defines overall structure and characterizes obligations on the derived class as pure virtual functions. The base class can depend on an implementation of these pure virtuals in the derived class, as the derived class can invoke (and reuse) the base class member functions. [Coplien, p268]
"A client might want to inherit a class's implementation but not its interface. Private derivation accomplishes this kind of inheritance. A client might want to inherit both a class's interface and its implementation. Public derivation accomplishes this kind of inheritance." [Martin Carroll, Margaret Ellis, Designing and Coding Reusable C++, Addison-Wesley, 1995, p178]
Use private inheritance judiciously. A common usage is to combine public inheritance of an interface with private inheritance of an implementation. [Meyers, 50 Ways, pp145-150,164]
Private inheritance models "implemented in terms of". Since this can be accomplished by simple containment ("has a"), I see no reason to use this kind of inheritance and eschew it religiously. It muddles your type with your implementation to save you a few keystrokes. Minor changes to your keystroke count are much less important than the clarity of your class to your users and to others who maintain it. [Ken Arnold, p67]
Favor object composition over class inheritance. [GOF, p20]
"Do not use private inheritance. It is not an object-oriented concept and it is not used in any object-oriented design methodology." [Karlsson, p331]
Use multiple inheritance rarely. It may cause difficulties when reusing components. Most object-oriented languages have problems when managing multiple inheritance, especially if the inheritance structure is ambiguous. Multiple inheritance is not common in the real world and it is important to focus on modelling the real world. [Karlsson, p312]
Use multiple inheritance judiciously. The one indisputable fact about multiple inheritance in C++ is that it opens up a Pandora's box of complexities that simply do not exist under single inheritance. Of these, the most basic is ambiguity. Suppose: a Lottery class has the method draw(), a GraphicalObject class has the method draw(), and a LotterySimulation class wants to inherit from both. Everything is fine until the client of LotterySimulation calls the draw() method - the compiler will not accept this ambiguity. [The solution is to provide a "fully qualified path" to the draw() that is desired.] Another sore spot is the "deadly diamond inheritance hierarchy". Using virtual base classes mostly remedy the problems, but they introduce their own suite of complexities. Limiting the bulk of your base classes to the pure abstract base class form routinely makes things much more manageable. [Meyers, 50 Ways, pp157-169]
Classes in the Taligent environment are artificially partitioned into two categories: domain classes that represent fundamental abstractions (like a car), and mixin classes that represent optional functionality (like power steering). Taligent suggests the rule:
A class may inherit from zero or one domain classes, plus zero or more mixin classes.The effect of this guideline is that domain classes form a conventional, tree-structured inheritance hierarchy rather than an arbitrary acyclic graph. This makes the domain class hierarchy much easier to understand. Mixins then become add-in options that do not fundamentally alter the inheritance hierarchy. [Taligent, p17]
You can, and should, use multiple inheritance in other ways as well if it makes sense. [Taligent]
Favor object composition over class inheritance. [GOF, pp19-20]
Programmers modify the behavior of white-box frameworks by applying inheritance to override methods in subclasses of framework classes. Because it is necessary to override methods, programmers have to understand the framework's design and implementation, at least to a certain degree of detail. Black-box frameworks offer ready-made components for adaptations. Modifications are done by composition, not by inheritance. Frameworks will evolve more and more into black-box frameworks as they mature. [Wolfgang Pree, "Essential framework design patterns", Object Magazine, Mar 97, p34]
Future frameworks will rely mainly on composition. Frameworks that rely on inheritance are susceptible to the "fragile base class problem" - changes to a base class can fracture all the classes inheriting from it. This can be alleviated by designing frameworks in a way that allows most adaptations by composition instead of inheritance. [Wolfgang Pree, "Frameworks - past, present, future", Object Magazine, May 96, p25]
Dynamic, run-time simulation of inheritance can be done with something like delegation. Delegation is to object instances what inheritance is to classes: the ability to share code and automatically use one abstraction's code to field requests made of another. [Coplien, p160]
Delegation is a way of making composition as powerful for reuse as inheritance. In delegation, two objects are involved in handling a request: a receiving object delegates operations to its delegate. This is analogous to subclasses deferring requests to parent classes. Delegation is an extreme example of object composition. It shows that you can always replace inheritance with object composition as a mechanism for code reuse. [GOF, p20]
"Templates and inheritance are both powerful features of C++. Unfortunately, there is some confusion among C++ programmers about whether to base a design for a class hierarchy on templates or on inheritance. Library designers sometimes use inheritance when use of templates would produce a better design." The drawbacks of inheritance-based container classes discussed here include:
The question you must ask yourself is this: does the type I am trying to model in my domain actually affect the behavior of the class in my design? If the type does not affect the behavior, you can use a template. If the type does affect the behavior, you'll need virtual functions, and you'll therefore use inheritance ... The hallmark of a template class: the behavior doesn't depend on the type at all. [Meyers, 50 Ways, pp150-157]
Here's a rule of thumb to try for choosing between inheritance and templates -
For the inheritance relationship to be worthwhile, we need to have reasons to treat objects of the two classes interchangeably.
If the two classes are instantiations of a single template, however, no such relationship holds between them. The types are not related, they have no common ancestor, and code that manipulates objects of one type will not expect to manipulate objects of the other type. Indeed, the types of the classes are not related at all; only the implementations of those types are shared.
Think of templates as automated cut-and-paste, or macros on steriods. They allow sharing of code, but only in the sense of sharing sequences of characters that go through the compiler.
Sharing code on the inside of a class should not be confused with inheritance, which makes a much stronger claim. When two classes are related by inheritance, they share far more than the accidental features of the code that is used to implement them. Such classes are bound so closely that at some point they are indistinguishable to the code that manipulates instances of the classes. If that is the relationship you want to reflect, get your code reuse by inheritance. If you just want to make sure you don't have to copy all the changes, however, use templates.
[Source: Jim Waldo, "Choose your reuse", Unix Review, Dec 93, pp73-76]