|
||||
25.5 USING INHERITANCE: A TAXONOMY OF TAXONOMYThe power of inheritance comes from its versatility. True, this also makes it scary at times, causing many authors to impose restrictions on the mechanism While understanding these fears and even sometimes sharing them --- do the boldest not harbor the occasional doubt and anxiety? --- we should overcome them and learn to enjoy inheritance under all of its legitimate variants, which will now be explored. After recalling some commonly encountered wrong uses of inheritance we will individually review the valid uses:
Some of these categories (subtype, view, representation, facility) raise specific issues and will be discussed in more detail in separate sections. Scope of the rulesThe relatively broad view of inheritance taken in this book in no way means that "anything goes". We accept and in fact encourage certain forms of inheritance on which some authors frown; but of course there are many ways to misuse inheritance, and not just CAR_OWNER. So the inevitable complement of our broad-mindedness is a particularly strict rule:
------------------------------------------------------------------------- Inheritance rule Every use of inheritance should belong to one of the accepted categories. ------------------------------------------------------------------------- This rule is stern indeed: it states that the types of use of inheritance are known and that if you encounter a case that is not covered by one of these types you should just not use inheritance. What are "the accepted categories"? The implicit meaning is "the accepted categories, as discussed in the rest of this section". I indeed hope that all meaningful uses are covered. But the phrasing is a little more careful because the taxonomy may need further thinking. I found precious little in the literature about this topic; the most useful reference is an unpublished PhD thesis ([Girod 1991]), and none of the popular so-called O-O methodology books yielded much of use. So it is quite possible that in this attempt at classification has missed some categories. But the rule indicates that if you see a possible use of inheritance that does not fall into one of the following categories, you should give it serious thought. Most likely you should not use inheritance in that case; if after further reflection you are still convinced that inheritance is appropriate, and you are still unable to attach your example to one of the categories of this chapter, then you may have a new contribution to the literature. We already saw a consequence of the Inheritance rule: the Taxomania rule, which states that every heir class should redeclare or introduce a feature, or change some assertion. It follows directly the observation that every legitimate form of inheritance detailed below requires the heir to perform at least one of these operations.
The Inheritance rule does not specify a certain inheritance link may belong to more than one of the inheritance categories listed below. In general it should not: -------------------------------------------------------------------------- Inheritance Simplicity rule A use of inheritance should preferably belong to just one of the accepted categories. -------------------------------------------------------------------------- This is not an absolute rule but what an earlier discussion called an "advisory positive". The rationale for the rule is once again the desire for simplicity and clarity: if whenever you introduce an inheritance link between two classes you apply explicit methodological principles, and in particular decide which one of the approved variants you will be using, you are much less likely to make a design mistake or to produce a messy, hard-to-use and hard-to-maintain system structure.
A compelling argument does not seem to exist, however, for making the rule absolute, and once in a while it may be convenient to use a single inheritance link for two of the goals captured by the classification. Such cases remain a minority. Unfortunately I do not know of a simiple criterion that would unambiguously tell us when it is all right to collapse several inheritance categories into one link. Hence the advisory nature of the Inheritance Simplicity rule. The reader's judgment, based on a clear understanding of the methodology of inheritance, should decide any questionable case. Wrong usesThe preceding two rules confirm the obvious: that it is possible to misuse inheritance. Here is a list of typical mistakes, most of which have already been mentioned. Human ability for mischief being what it is, we can in no way hope for completeness, but a few common mistakes are easy to identify. The first is "has" relation with no "is" relation. This was illustrated by the CAR_OWNER example --- extreme but not unique. Over the years I have heard or seen a few similar ones, often as purported examples of multiple inheritance, such as APPLE_PIE inheriting from APPLE and from PIE, or (this one reported by Adele Goldberg) ROSE_TREE inheriting from ROSE and from TREE. Another is a typical case of taxomania in which a simple boolean property, such as a person's gender (or a property with a few fixed values, such as the color of a traffic light) is used as inheritance criterion even though no significant features depend on it. A third typical mistake is convenience inheritance, in which the developer sees some useful features in a class and inherits from that class simply to reuse these features. What is wrong here is neither the act of "using inheritance for implementation", nor "inheriting a class for its features", both of which are perfectly acceptable forms of inheritance studied later in this chapter, but the use of a class as a parent without the proper is-a relationship between the corresponding abstractions --- or in some cases without adequate abstractions at all. General taxonomyOn now to the valid uses of inheritance. The list will include twelve different categories, conveniently grouped into three broad categories:
The classification is based on the observation that any software system reflects a certain external model, itself connected with some outside reality in the software's application domain. Then we may distinguish:
These three general categories facilitate understanding, but the most important properties are captured by the final categories (the leaves on the preceding figure). Since the classification is itself a taxonomy, you may want to ask yourself, out of curiosity, how the identified categories apply to it. This is the topic of an exercise.
The definitions which follow all use the names A for the parent class and B for the heir.
Each definition will state which of A and B is permitted to be deferred, and which effective. A table at the end of the discussion recalls the applicable categories for each deferred-effective combination. Subtype inheritanceWe start with the most obvious form of model inheritance: ----------------------------------------------------------------------------- Definition: Subtype inheritance Assuming a software system associated with a model of some other system containing external objects, subtype inheritance applies if A and B represent certain sets A' and B' of such external objects, A is deferred, B' is a subset of A', and the set modeled by any other subtype heir of A is disjoint from B'. ----------------------------------------------------------------------------- In most practical cases the "other system" will be non-software, for example some aspect of a company's business (where the external objects might be checking and saving accounts) or some part of the physical world (where the external objects might be planets and stars). Subtype inheritance is the form of inheritance that is closest to the hierarchical taxonomies of botany, zoology and other natural sciences (VERTEBRATE MAMMAL and the like). A typical software example is CLOSED_FIGURE ELLIPSE. We insist that the parent, A. must be deferred, so that it describes a non-completely specified set of objects. B, the heir, may be effective, or it may still be deferred. The next two categories cover the case in which A may be effective. A later section will explore in more detail this inheritance category, not always as straightforward as it would seem at first.
Restriction inheritance
----------------------------------------------------------------------------- Definition: Restriction inheritance Restriction inheritance applies if the instances of B are those instances of A that satisfy a certain constraint, expressed if possible as part of the invariant of B and not included in the invariant of A. Any feature introduced by B should be a logical consequence of the added constraint. A and B should be both deferred or both effective. ----------------------------------------------------------------------------- Typical examples are RECTANGLE SQUARE, where the extra constraint is side1 = side2 (included in the invariant of SQUARE), and ELLIPSE CIRCLE, where the extra constraint is that the two foci of an ellipse are the same point for a circle ; in the general case an ellipse is the set of points such that the sum of their distances to the two foci is equal to a certain constant.) Many mathematical examples indeed fall into this category. The last part of the definition is meant to avoid mixing this form of inheritance with others, such as extension inheritance, which may add completely new features in the heir. Here to keep things simple it is preferable to limit new features, if any, to those that directly follow from the added constraint. For example class CIRCLE will have a new feature radius which satisfies this property: in a circle, all points have the same distance from the merged center, and this distance deserves the status of a feature of the class, whereas the corresponding notion in class ELLIPSE (the average of the distances to the two foci) is probably not significant enough to have yielded a feature. Because the only conceptual change from A to B is to add some constraints, the classes should be both deferred or both effective. Extension inheritance
-------------------------------------------------------------------------- Definition: Extension inheritance Extension inheritance applies when B introduces features not present in A and not applicable to direct instances of A. A must be effective. -------------------------------------------------------------------------- The presence of both the restriction and extension variants is one of the paradoxes of inheritance. As noted at the very beginning of the discussion of inheritance, extension applies to features whereas restriction (and more generally specialization) applies to instances, but this does eliminate the paradox. The problem is that the added features will usually include attributes. So if we take the naïve interpretation of a type (as given by a class) as the set of its instances, then it seems the subset relation is the wrong way around! Assume for example class A feature a1: INTEGER end class B inherit A feature b1: REAL end Then if we view each instance of A as representing a singleton, that is to say a set containing one integer (which we can write as <n> where n is the chosen integer) and each instances of B as a pair containing an integer and a real (such as the pair <1, ---2.5>), the set of pairs MB is not a subset of the set of singletons MA. In fact, if we absolutely look for a subset relation, it will be in the reverse direction: there is a one-to-one mapping between MA and the set of all pairs having a given second element, for example 0.0.
This discovery that the subset relation seems to be the wrong way may make extension inheritance look suspicious. For example an early object-oriented library had RECTANGLE inheriting from SQUARE (not the other way around as we have learned): SQUARE has a side attribute; RECTANGLE inherits from SQUARE and adds a new feature, other_side, so this is inheritance for you! This was criticized and soon corrected. But we cannot dismiss the general category of extension inheritance. In fact its equivalent in mathematics, where we specialize a certain notion by adding completely new attributes (or their mathematical counterparts) is frequently used and everyone considers it perfectly legitimate. A typical example is the notion of ring, specializing the notion of group. A group has a certain operation, say +, with certain properties. A ring is a group, so it also has + with these properties, but it adds a new operation, say *, with extra properties of its own. This is not fundamentally different from introducing a new attribute in an heir software class. In object-oriented software, the case in fact happens often. In most applications, of course, SQUARE should inherit RECTANGLE, not the reverse; but it is not difficult to think of more legitimate examples. For example a class MOVING_POINT (for cinematics applications) might inherit from a purely graphical class POINT and add a feature speed describing the speed's magnitude and direction; or, in a text processing application, a class CHAPTER might inherit from DOCUMENT, adding the specific features of a document which is a chapter in a book, such as its current position in the book and a procedure that will reposition it. A proper mathematical model(Non-mathematically-inclined readers should skip this section.) For peace of mind we must resolve the apparent paradox noted earlier (the discovery that MB is not a subset of MA) since we do want some subset relation to hold between instances of an heir and instances of the parent. That relation does exist in the case of extension inheritance; what the paradox shows is that it is inappropriate to use cartesian product of the attribute types to model a class. Given a class class C feature c1: T1; c2: T2; c3: T3 end we should not take, as a mathematical model C' for the set of instances of C, the cartesian product T'1 T'2 T'3, where the prime signs ' indicate that we recursively use the model sets; this would lead to the paradox (among other disadvantages). Instead, we must consider any instance as being a partial function from the set of possible attribute names ATTRIBUTE to the set of all possible values VALUE, with the following properties:
Then if we remember that a function is a special case of a relation, and that a relation is a set of pairs (for example an instance of class A may be modeled by the function {<a1, 25>}, and the instance of B used as an earlier example is modeled by {<a1, 1>, <b1, ---2.5>}), then we do have the expected property that B' is a subset of A. Note that it is essential to state the property A1 as "The function is defined for...", not "The function domain is...". This is what ensures that an element of the model set for the heir also belongs to the model set for a parent. As a result, every software object is modeled by an infinity of (finite) mathematical objects. This discussion has only given a sketch of the mathematical model. For more details on using partial functions to model tuples, and the general mathematical background, see [M 1990]. Variation inheritance(Non-mathematical readers, welcome back!) We now move to the second of our three broad groups of inheritance categories: variation inheritance.
--------------------------------------------------------------------------- Definition: Functional and type variation inheritance Variation inheritance applies if B redefines some of the features of A; A and B are either both deferred or both effective, and B must not introduce any new features except for the direct needs of the redefined features. There are two variants: Functional variation inheritance: some of the redefinitions affect feature bodies, rather than just their signatures. Type variation inheritance: all redefinitions are signature redefinitions. --------------------------------------------------------------------------- Variation inheritance is applicable when an existing class A, describing a certain abstraction, is already useful by itself, but you discover the need to represent a similar but not identical abstraction, which essentially has the same features but with some different signatures or implementations. The definition requires that both classes be effective (the more common case) or both deferred: variation inheritance does not cover the case of an effecting, where we transform a notion from abstact to concrete. A closely related category is uneffecting, studied next, in which some effective features are made deferred. The definition stipulates that the heir should introduce no new features, except as directly needed by the redefined features. This clause distinguishes variation inheritance from extension inheritance. In type variation inheritance we only change the signatures of some features (recall that a feature's signature is the number of its arguments, their types, and the type of its result if any). This form of inheritance is suspect; it is usually a sign of over-taxonomy, where you introduce variants which are not really needed. In legitimate cases, however, it may be a preparation for extension inheritance or implementation variation inheritance. An example of type variation inheritance might be the heirs MALE_EMPLOYEE and FEMALE_EMPLOYEE. In functional variation inheritance we change some of the features' bodies; if, as is usually the case, the features were already effective, this means changing their implementation. The features' specification, as given by assertions, may also change. It is also possible, although less common, to have functional variation inheritance between two deferred classes; in that case the assertions will change. This may imply changes in some functions, deferred or effective, used by the assertions, or even the addition of new features as long as this is for the "direct needs of the redefined features" as the definition states. Functional variation inheritance is the direct application of the Open-Closed principle: we want to adapt an existing class without affecting the original (of which we may not even have the source code) and its clients. It is subject to abuses since it may be a form of hacking: twisting an existing class so as to fit a slightly different purpose. At least this will be disciplined hacking, which avoids the dangers of directly modifying existing software, as analyzed in the discussion of the Open-Closed principle. But if you do have access to the source code of the original class, you should examine whether it is not preferable to reorganize the inheritance hierarchy by introducing a more abstract class of which both A (the existing variant) and B (the new one) will both be heirs, or proper descendants with peer status.
Type variation inheritance is necessary only if some of the original signatures did not make enough use of anchored (like ...) declarations. For example in the SEGMENT class of an interactive drawing package you may have introduced a function perpendicular: SEGMENT is -- Segment of same length and same middle point, rotated 90 degrees ... and then want to define an heir DOTTED_SEGMENT to provide a graphical representation with a dotted line rather than a contiguous one. In that class, perpendicularshould return a result of type DOTTED_SEGMENT, so you will need to redefine the type. This would all be avoided if the original returned a result of type like Current, and if you have access to the source of the original and the authority to modify it you should update that type declaration, normally without any adverse effect on existing clients. But if for some reason you cannot modify the original, or if an anchored declaration is not appropriate in that original (perhaps because of the needs of other descendants), then the ability to redefine the type can save your neck. Uneffecting--------------------------------------------------------------------- Definition: Uneffecting inheritance Uneffecting inheritance applies if B redefines some of the effective features of A into deferred features. --------------------------------------------------------------------- Uneffecting is not common, and should not be. Its basic idea goes against the normal direction of inheritance, since we usually expect B to be more concrete and A more abstract (as with the next category, reification, for which A is deferred and B effective or at least less deferred). For that reason beginners should stay away from uneffecting. But it may be justified in the following two cases:
For a link of the uneffecting category, B will be deferred; A will normally be effective, but might be partially deferred. Reification inheritanceWe now come to the third and last general group, software inheritance. ------------------------------------------------------------------------------ Definition: reification inheritance Reification inheritance applies if A represents a general kind of data structures, and B represents a partial or complete choice of implementation for that data structure. A is deferred; B may still be deferred, leaving room for further reification through its own heirs, or it may be effective. ------------------------------------------------------------------------------ An example, used several times in earlier chapters, is a deferred class TABLE describing tables of a very general nature. Reification leads to heirs SEQUENTIAL_TABLE and HASH_TABLE, still deferred. Final reification of SEQUENTIAL_TABLE leads to effective classes ARRAYED_TABLE, LINKED_TABLE, FILE_TABLE. The term "reification", from latin words meaning "making into a thing", comes from the literary criticism of Georg Lukács. In computing science it is used as part of the VDM specification and development method. Structure inheritance--------------------------------------------------------------------------- Definition: structure inheritance Structure inheritance applies if A, a deferred class, represents a general structural property and B, which may be deferred or effective, represents a certain type of objects possessing that property. --------------------------------------------------------------------------- Usually the structural property represented by A is a mathematical property that a certain set of objects may possess; for example A may be the class COMPARABLE, equipped with such operations as infix "<" and infix ">=", representing objects to which a total order relation is applicable. A class that needs an order relation of its own, such as STRING, will inherit from COMPARABLE. It is common for a class to inherit from several parents in this way. For example class INTEGER in the Kernel Library inherits from COMPARABLE as well as from a class NUMERIC (with features such as infix "+" and infix "*") representing its arithmetic properties. (Class NUMERIC more precisely represents the mathematical notion of ring.) What is the difference between the structure and reification categories? With reification inheritance B represents the same notion as A, with more implementation commitment; with structure inheritance B represents an abstraction of its own, of which A covers only one aspect, such as the presence of an order relation or of arithmetic operations. Implementation inheritance--------------------------------------------------------------------------------- Definition: implementation inheritance Structural inheritance applies if B obtains from A a set of features (other than constant attributes and once functions) necessary to the implementation of the abstraction associated with B. Both A and B must be effective. --------------------------------------------------------------------------------- Implementation inheritance will be discussed in detail later in this chapter. A common case is what will be called the "marriage of convenience", based on multiple inheritance, where one parent provides the specification (reification inheritance) and the other the implementation (implementation inheritance).
The case of inheriting constant attributes or once functions is covered by the next variant. Facility inheritanceFacility inheritance is the scheme in which the parent is a collection of useful features meant only for use by descendantsa class becomes an heir of another chiefly for the be
----------------------------------------------------------------------------- Definition: facility inheritance Facility inheritance applies if A exists solely for the purpose of providing a set of logically related features for the benefit of heirs such as B. Two common variants are: Constant inheritance in which the features of A are all constants or once functions describing shared objects. Machine inheritance in which the features of A are routines, which may be viewed as operations on an abstract machine. ----------------------------------------------------------------------------- An example of facility inheritance was provided by class EXCEPTIONS, a utility class providing a set of facilities for detailed access to the exception handling mechanism.
Sometimes, as in the examples given later in this chapter, a link of the facility kind uses only one of the two variants, constant or machine; but in others, such as EXCEPTIONS, the parent class provides both constants (such as the exception code Incorrect_inspect_value) and routines (such as trigger to raise a developer exception). Since this discussion is meant to introduce disjoint inheritance categories, we should treat facility inheritance as a single category --- with two (non-disjoint) variants. With constant inheritance, both Aand B are effective. With machine inheritance, there is more flexibility, but B should be at least as effective as A. Facility inheritance is discussed in detail later in this chapter.
Using inheritance with deferred and effective classesEach of the various categories reviewed places some
requirements on which of the heir and the parent may be deferred and which may
be effective. The following table summarizes the rules. "Variation" covers type
variation and functional variation. Items marked l appear in more than
one entry.
PREVIOUS SECTION
---- NEXT SECTION
|