Position

Inheritance, which allows new items to be defined with respect to already existing items, is now widely used in software construction. In fact, inheritance is almost universally regarded as a cornerstone of methods termed ``object-oriented'' [#!fichman!#]. One reason for the success of inheritance is the fact that it meshes with human cognitive abilities—new entities are defined by saying how they are different from existing entities, which is very similar to the way people seem to learn by association, learn rules of generality, and learn exceptions to such rules.

In a programming context, inheritance has many beneficial uses, but merging them all into a single hierarchy can bring out serious conflicts. Many OO practitioners know that certain inheritance practices are not advisable, such as hiding or removing methods in descendant classes, or arbitrarily redefining method semantics, because subclasses that do not behave as expected may be produced. What is the real reason for such conflicts, and why are they allowed in current OO programming languages? An answer can be found by examining the different applications of inheritance, and who makes use of them.

Figure: A Taxonomy of Uses for Inheritance
\begin{figure}
\setlength{\unitlength}{1.0in}
% \newlength{\mybls}
\setlengt...
...ength{\baselineskip}{-3pt}
║║║\lq\lq Like''
║║ }}}
║%
\par
\end{picture}
\end{figure}

Figure [*] depicts a taxonomy of the various uses for inheritance in object oriented programming (OOP). This taxonomy is not meant to be exhaustive, but it does illustrate several interesting divisions. The primary partition in this figure is between inheritance in the implementer's dimension and inheritance in the user's dimension [#!lalonde:exemplars:89!#]. From the implementer's perspective, inheritance has many uses in creating or defining new modules, classes, or objects. From the client's perspective, inheritance has many uses in understanding, applying, and reasoning about classes.

Note that for the remainder of this discussion, I will use the term class to denote a software artifact defined within an inheritance framework. Similarly, the terms child class or subclass will be used to refer to a software artifact that inherits from some parent class or superclass. I have chosen these terms since they are commonly used and understood, but I do not wish to imply that only class-based inheritance systems will be considered [#!lalonde:exemplars:89!#].

From the implementer's perspective, inheritance is often used as a method of programming by difference. This programming style allows implementers to define new classes differentially, simply by capturing how they differ from some pre-existing class. Traditionally, this capability has been put forth as one of the key benefits provided by OO languages, and as one of the main sources of reuse in OO environments.

The implementer can use inheritance to define the specification of a new class, the implementation of a new class, or both. Further, when defining the specification of a class by difference, the implementer can treat the parent class in one of two separate ways:

From the client's perspective, inheritance is not used to define new classes, it is used to understand and apply existing classes. Inheritance can be used within the language definition to determine how classes can be used in language constructs. In this vein, inheritance is most often used for two purposes. First, it is used in the execution model for many OO languages to describe the notion of dynamic binding, and how that determines what code operations are executed. Second, inheritance is very commonly used to determine type conformance for parameters to operations. Most languages that use this approach choose to interpret inheritance relations as ``is-a'' or subtype relations. A class B is a subtype of another class A (B is-a A) if any instance of B can be used wherever instances of A are required [#!lalonde:exemplars:89!#]. The combination of subtyping and dynamic binding is often the key to providing polymorphism in OO languages.

In addition to defining the way classes are used within language constructs, inheritance can also be used to enhance the way people reason about and use classes. In [#!lalonde:exemplars:89!#], three distinct forms of inheritance that are useful for clients are described: is-a relationships, specialization (or generalization) relationships, and ``like'' or similarity relationships. The is-a relation that has already been described can clearly be used by clients to lower the cognitive load of reasoning about collections of classes. A similar relation, that a class B is a specialization of another class A, can also be used by clients as an aid to understanding. A class B is a specialization of A if the instances of B can be obtained from those of A through some form of restriction. [#!lalonde:exemplars:89!#] gives the following examples of specialization:

... Strings can be viewed as specializations of arrays in which the elements must be characters, arrays can be viewed as specialization[s] of dictionaries in which the keys (subscripts) must be positive integers. [#!lalonde:exemplars:89!#, p. 219]
Finally, the ``like'' relationship implies that two classes are the same except for some clearly delineated differences. A set is like a bag, but it does not permit duplicates to be inserted. This relation can be used to structure a collection of classes into a hierarchy that promotes understanding and careful ``chunking'' of differences (as opposed to a hierarchy set up for strict subtyping, or for defining specifications by difference).

Note that in typical practice (e.g., C++, Eiffel), an OO language only only supports a single inheritance hierarchy and all of these alternatives are blended together in this single language mechanism. Programmers working in OO environments most likely focus on inheritance as a balance between a subtype hierarchy, a code inheritance hierarchy, and a (syntactic) interface inheritance hierarchy.

From the point of view of supporting modular reasoning about programs, the uses of inheritance depicted in Figure [*] pose many challenges. When reasoning about the correctness of a given class C, one must wear the hats of both implementer and client:

Thus, all of the uses of inheritance in Figure [*] must be considered—not just those seen from the client's perspective.

Unfortunately, formal treatments of inheritance often center around an is-a interpretation of the inheritance hierarchy, which fails to address all of the inheritance uses discussed above. This interpretation is also at odds with the way inheritance is used in practice. If the interpretation were correct, and only true is-a relations were permitted in an inheritance hierarchy, modular reasoning would then be feasible. This is the approach taken in RESOLVE, where an implementer may only make use of inheritance through a very tight interpretation of abstract model specification inheritance (at the lower left of Figure [*]). This restriction ensures that the single inheritance hierarchy present can only contain is-a relations.

Most often, however, this restriction is not made. A typical OO language like Smalltalk, C++, or Eiffel contains a single inheritance mechanism which is used for many (or even all) of the purposes described in Figure [*]. This hopelessly muddles inheritance of implementation or representation details with the more abstract inheritance relations like subtyping. As a result, modular reasoning is not feasible, since the inheritance relationship itself does not have a stable meaning.

Further, class-based OO systems like Smalltalk, C++, and Eiffel pose specific problems for modular reasoning:

In general, a class mechanism enforces the restriction that all objects of a specific class have the same representation. [#!lalonde:exemplars:89!#, p. 214]

Neither [class-based system] separates the two notions of specification and implementation completely because there is always a one-to-one correspondence between the two. [#!lalonde:exemplars:89!#, p. 227]
The intertwining of specification and implementation concepts within a language can hamper modular reasoning because it allows implementation details to ``creep in'' to specifications.

In C++ and Eiffel, deferred or virtual classes are used to further separate specifications and implementations by placing the specification in a (virtual) superclass distinct from the concrete implementation (in a subclass). Disciplined use of this technique can remove the blurring of specifications and implementations inherent in class-based inheritance, but does not completely address the other problems limiting modular reasoning.

It appears that it might be possible to support modular reasoning within an inheritance framework, but only if conflicting uses of inheritance are decoupled by placing them in distinct hierarchies, and if the guarantees that must be made about information hiding along inheritance relations are completely spelled out. Without this approach, it appears that the scope of uses for inheritance must be significantly limited to make modular reasoning feasible.