Inheritance Hierarchies

As already mentioned in previous sections, inheritance is one of the basic properties of object-oriented languages and is in part responsible for the strength of the object-oriented paradigm. We will now give some more details of its significance and properties, most of which are necessary to understand particular concepts that will be presented in next chapters.

Inheritance implies both extension and contraction. Because a derived class behavior is, strictly speaking, broader than that of its parents we can say that the child class is an extension of its parent classes. And because the derived class can override some of its base class behavior, it is also a contraction.

The main reasons for using inheritance is that it provides both code and concept reuse. Code reuse because the operations implemented in the base class are available in its derived classes without needing to add any extra code. But most importantly, concept reuse because even if methods in the base class are overridden and specialized, the concept that they represent is reused and a better abstraction management is possible. Other benefits of using inheritance are that it enhances robustness, it gives consistency to the interface, it couples well with rapid prototyping techniques, and it promotes information hiding and encapsulation.

But inheritance may also imply some disadvantages. Among the most commonly listed we find that it may compromise execution speed and efficiency (but see next section for a more detailed discussion on this issue), it introduces an abstraction effort overhead in the design phase, it compromises the system flexibility once the hierarchy is established, it tends to de-localize code (responsibility may be distributed in such a way that it may be difficult to identify who does what and where), and it may even introduce some code complexity. Nevertheless, all these inconvenients can be minimized to non-noticeable levels by a an organized and disciplined use of inheritance and the use of supporting technologies such as design patterns[Gamma et al., 1995], unit testing, and CASE tools.

An abstract class is defined as a class that cannot be instantiated and is therefore only used in the context of an inheritance hierarchy. An abstract class cannot be instantiated because it has non-defined behavior (abstract or pure virtual methods) that must be defined in any non-abstract derived class. An interface class is one that has no defined behavior. Therefore all its methods are abstract and it can be considered as an extreme case of the abstract class. Obviously an interface class cannot be instantiated either and is only used as a way of specifying an interface to which all derived classes should conform. It is indeed a clear example of concept reuse as opposed to code reuse in the object-oriented paradigm.

If we look at the origin and properties for the inheritance relationships, we may classify them into different categories:

Out of all these kinds of inheritance the first two are by far the more common and most of the others can be see as a special case of one of them. The last three are not recommended. On the other hand, while the origin and intent is different in all of the different inheritance kinds, the result of applying them may be indistinguishable. Particularly, by looking at an inheritance hierarchy it is usually impossible to decide whether it has been the result of a generalization or specialization process. In a generalization process the derived classes exist before and the base class is obtained by realizing the commonalities in them. In the specialization process the base class is ``broken down'' into different derived classes.

Inheritance hierarchies allow to manage a system complexity. We want all branches in an inheritance hierarchy to be disjoint and balanced. Disjoint means that any given object may not be an instance of two of the subclasses found at the same level of the hierarchy1.1. By balanced we mean that two subclasses should represent comparable sized sets of objects, having a very general class and a very particular one at the same level of the hierarchy is not a good idea.

Inheritance can also be classified into static or dynamic. In a static specialization an object belonging to a particular level of the hierarchy is not intended to change in run-time from one subclass to another. In a dynamic specialization the belonging of the object to a particular subclass depends on its state and therefore can change on run-time. Dynamic inheritance, although theoretically correct, introduces many practical problems and is often substituted, following the delegation principle, by an association and a static inheritance.

Delegation can be used in other situations in order to reduce coupling. This mechanism introduces another indirection and therefore the client needs not to know the provider and this provider can change dynamically. Delegation also allows multiple inheritance to be implemented in single inheritance programming languages.