Perhaps the most difficult problem of using inheritance arises when several alternative criteria are available to classify the abstractions of a certain application area.
The traditional classifications of the natural sciences use a single criterion (possibly involving several qualities) at each level: vertebrate versus invertebrate, leaves renewed each year or not, and so on. The result is what we would call single inheritance hierarchies, whose main advantage is their great simplicity. But there are problems too, since nature is definitely not single-criterion. This will be obvious to anyone who has ever tried to take a nature walk armed with a botanical book meant to enable plant recognition through the official, Linnaean criteria. Species A is deciduous and species B is not, the book says; how long can you afford to wait, if this is July, to find out whether the leaves remain? You are told that June will bring bright purple flowers, but how can you tell in the midst of January? The roots of A are at most 7 meters deep, versus at least 9 for B --- must you dig?
In software, when a single criterion seems too restrictive, you can use all the techniques of multiple and especially repeated inheritance that you have learned to master in earlier chapters. Assume for example a class EMPLOYEE in a personnel management system. Assume further that we have two separate criteria for classifying employees:
and that both of these criteria have been recognized to lead to valid descendant classes; in other words you are not engaging into what a later section of this chapter will call taxomania (the disease of inagining inheritance nodes everywhere), but the classes that you have identified, such as TEMPORARY_EMPLOYEE for the first criterion and MANAGER for the second, are truly characterized by specific features not applicable to the other categories. What do you do?
A first attempt would be to introduce all the variants at the same level:
To keep this sketched example small and the figure simple, the class names have been abbreviated. To go from this example to a real system we would have to apply the usual naming guidelines, which suggest longer and more accurate names such as PERMANENT_EMPLOYEE, ENGINEERING_EMPLOYEE and so on.
This inheritance hierarchy is not satisfactory since widely different concepts are represented by classes at the same level.
If you retain the idea of using inheritance for the classification used in the example under discussion, you should introduce an intermediate level to describe the competing classification criteria:
Note that the name CONTRACT_EMPLOYEE does not mean "employee that has a contract" (as opposed to employees that might not have one!), but "employee as characterized by his contract". The name of the sibling class similarly means "employee as characterized by his specialty".
That these names seem far-fetched reflects a certain uneasiness, typical of this kind of inheritance. In subtype inheritance we encountered the rule that the sets of instances represented by the various heirs to a class be disjoint. Here the rule does not apply: a permanent employee, for example, may be an engineer too. This means that such a classification is meant for repeated inheritance: some proper descendants of the classes shown on the figure will have both CONTRACT_EMPLOYEE and SPECIALTY_EMPLOYEE as ancestors --- not necessarily directly, but for example by inheriting from both PERMANENT and ENGINEER. Such classes will be repeated descendants of EMPLOYEE.
This form of inheritance may be called view inheritance: various heirs of a certain class represent not disjoint subsets of instances (as in the subtype case) but various ways of classifying instances of the parent. Note that this only makes sense if both the parent and the heirs are deferred classes, that is to say, classes describing general categories rather than fully specified objects. Our first attempt at EMPLOYEE classification by views (the one that had all descendants at the same level) violated that rule; the second one satisfies it.
View inheritance is relatively far from the more common uses of inheritance and is subject to criticism. The reader will be judge of whether to use it for his own purposes, but in any case we should examine the pros and cons.
It should be clear that --- like repeated inheritance, which it requires --- view inheritance is not a beginner's mechanism. The rule of prudence that was introduced for repeated inheriance holds here: if you have less than six months' hands-on experience with object-oriented development of significant projects, stay away from view inheritance.
The alternative to view inheritance is to choose one of the classification criteria as primary, and use it as the sole guide for devising the inheritance hierarchy; to address the other criteria, you will use specific features. It is interesting to note that modern zoology and botany use this approach: the basic classification criterion is the reconstructed evolutionary history of the genera and species involved. Would it that we always had such a single, indisputable standard to guide us in devising software taxonomies.
To stick to a single primary criterion in our example we could decide that the job type is the factor of principal interest, and represent the employment status by a feature. As a first attempt, the feature (in class EMPLOYEE) could be
but this is dangerously limitative; to extend the possibilities, we could have
Permanent: INTEGER is unique; Temporary: INTEGER is unique; Contractor: INTEGER is unique; ...
but then we have learned to be wary, for good reasons, of explicit enumerations. A much better approach is to introduce a class WORK_CONTRACT, most likely deferred, with as many descendants as necessary to account for specific kinds of work contract. Then we can stay away from loathed explicit discriminations of the form
if is_permanent then ... else ... end
inspect contract_type when Permanent then ... when ... ... end
with their contingent of future extendibility troubles (stemming from their violation of just about every modularity principle: continuity, single choice, open-closedness); instead, we will equip class WORK_CONTRACT with deferred features representing contract-type-dependent operations, which will then be effected differently in descendants. Most of these features will need an argument of type EMPLOYEE, representing the employee to which the operation is being applied; examples might include hire and terminate.
The resulting structure will look like this:
This scheme, as you may have noted, is almost identical to the handle-based design described earlier in this chapter.
Such a technique may be used in place of view inheritance. It does complicate the structure by introducing a separate hierarchy, a new attribute (here contract) and the corresponding client relations. It has the advantage that the abstractions in such a hierarchy are beyond question (work contract, permanent work contract); with the view inheritance solution, the abstractions are clear too but a little trickier to explain ("employee seen from the perspective of his work contract", "employee seen from the perspective of his specialty").
It is not uncommon to think of view inheritance early in the analysis of a problem domain, while you are still struggling with the fundamental concepts and considering several possible classification criteria, all of which vie for your attention. As you improve your understanding of the application area, it will often happen that one of the criteria starts to dominate the others, imposing itself as the primary guide for devising the inheritance structure. In such cases, the preceding discussion strongly suggests that you should renounce view inheritance in favor of more straightforward techniques.
I still find view inheritance useful when the following three conditions are met:
An example of application of these criteria is the uppermost structure of the Base libraries, in the environment described in the last part of this book. The resulting classes followed from a effort, pursued over many years and described in detail in the book [M 1994b], of applying taxonomical principles to the systematic classification of computing science's fundamental structures, in the tradition of Linnaeus and other scientists. The highest part of the "container" structure looks like this:
The first-level classification (BOX, COLLECTION, TRAVERSABLE) is view-based; the level below it (and many of those further below, not shown) is a subtype classification. A container structure is characterized through three criteria:
It is interesting in this example to note that the hierarchy did not start out as view inheritance. The initial idea was to use separate abstractions, to be combined through multiple inheritance; the classes BOX, COLLECTION and TRAVERSABLE, so that a class representing a particular data structure implementation --- such as a linked list, which is finite and unbounded (representation), sequentially accessed (access), and linearly traversable (traversal) --- would be obtained by multiply inheriting from the appropriate class in each of the three separate hierarchies:
But then we realized that it was inappropriate to keep BOX, COLLECTION and TRAVERSABLE separate: they all needed a few common features, in particular has (membership test) and empty (test for no elements). This clearly indicated the need for a common ancestor --- CONTAINER, where these common features now appear. Hence a structure that was initially designed as pure multiple inheritance, with three disjoint hierarchies at the top, turned out to be a view inheritance hierarchy with a considerable amount of repeated inheritance.
Although initially difficult
to get right, this structure has turned out to be useful, flexible and stable,
confirming both of the conclusions of this discussion: that view inheritance is
not for the faint of heart; and that when applicable it can play a central role
for complex problem domains where many criteria interact, if it is worth the
effort, as in a fundamental library of reusable components that simply
has to be done right.