|
||||
25.2 WOULD YOU RATHER BUY OR INHERIT?To choose between the two possible inter-module relations, client and inheritance, the basic rule is deceptively simple: client is has, inheritance is is. Why then is the choice not easy? To have and to beThe reason is that whereas to have is not always to be, in many cases to be is also to have. No, this is not some cheap attempt at existentialist philosophy, but simple observations on the difficulty of system modeling. We have already encountered an illustration of the first property --- to have is not always to be --- in the preceding example: a car owner has a car, but by no twist of reasoning or exposition can we assert that he is a car. What about the reverse situation? Take a simple statement about two object types from ordinary life, such as Every software engineer is an engineer. [A] whose truth we accept for its value as an example of the "is-a" relation (whatever the reader's opinion may be as to the statement's accuracy). It seems hard indeed to think of a case which so clearly expresses "to be" rather than "to have". But now consider the following rephrasing of the property: In every software engineer there is an engineer [B] which can in turn be restated as Every software engineer has an "engineer" component. [C] Twisted, yes, and perhaps a trifle bizarre in its expression; but not fundamentally different from our premise [A]! So here it is: by changing our perspective slightly we can rephrase the "is" property as a "has". If we look at the picture through the eyes of a programmer, we may summon an object diagram, in the style of those which served to discuss the dynamic model in an earlier chapter, showing a typical instance of a class and its components:
This shows an instance of SOFTWARE_ENGINEER with various subobjects, representing the various posited aspects of a software engineer's personality and tasks. Rather than subobjects (the expanded view) we might prefer to think in terms of references:
Both of these representations should be taken as ways to visualize the situation as seen from an implementation-oriented mindset, nothing else. But both clearly show that a client, or "has", interpretation --- every software engineer has an engineer as one of its parts --- is faithful to the original statement. The same observation can be made for any similar "is-a" relationship. So this is why the problem of choosing between client and inheritance is not trivial: when the "is" view is legitimate, one can always take the "has" view instead. The reverse is not true: when "has" is legitimate, "is" is not always applicable, as the CAR_OWNER example shows so clearly. This observation takes care of the easy mistakes, obvious to anyone having understood the basic concepts, and perhaps even explainable to authors of undergraduate texts. But whenever "is" does apply it is not the only contender. So two reasonable and competent people may disagree, one wanting to use inheritance, the other preferring client. Two criteria fortunately exist to help in such discussions. Not surprisingly (since they address a broad design issue) they may sometimes fail to give a clear, single solution. But in many practical cases they do tell you, beyond any hesitation, which of the two relations is the right one. Conveniently, one of these two criteria favors inheritance, and the other favors client. The rule of changeThe first observation is that the client relation usually permits change, while the inheritance relation does not. Here we must be careful with our use of the verbs "to be" and "to have" from ordinary language; so far they have helped us characterize the general nature of our two software relations, but software rules are, as always, more precise than their general non-software counterparts. One of the definining properties of inheritance is that it is a relation between classes, not objects. We have interpreted the property "Class B inherits from class A" as meaning "every B object is an A object", but must remember that it is not in the power of any such object to change that property: only a change of the class can achieve such a result. The property characterizes the software, not any particular execution. With the client relation, the constraints are looser. If an object of type B has a component of type A (either a subobject or an object reference), it is quite possible to change that component; the only restrictions are those of the type system, ensuring provably reliable execution (and governed, through an interesting twist, by the inheritance structure). So even though a given inter-object relationship can result from either inheritance or client relationships between the corresponding classes, the effect will be different as to what can be changed and what cannot. For example our fictitious object structure
could result from an inheritance relationship between the corresponding classes: class SOFTWARE_ENGINEER_1 inherit but it could just as well have been obtained through the client relation: class SOFTWARE_ENGINEER_2 feature the_engineer_in_me: ENGINEER ... end -- class SOFTWARE_ENGINEER_2 which could in fact be class SOFTWARE_ENGINEER_3 feature provided we satisfy the type rules by making class ENGINEER a descendant of class VOCATION. Strictly speaking the last two variants represent a slightly different situation from the first if we assume that none of the given classes is expanded: instead of subobjects, the "software engineer" objects will in the last two cases contain references to "engineer" objects, as in the second figure of page 747. The introduction of references, however, does not fundamentally affect this discussion. With the first class definition, because the inheritance relationship holds between the generating classes, it is not possible to modify the object relationship dynamically: once an engineer, always an engineer. But with the other two definitions such a modification is possible: a procedure of the "software engineer" class can assign a new value to the corresponding object field (the field for the_engineer_in_me or the_most_important_part_of_me). In the case of class SOFTWARE_ENGINEER_2 the new value must be of type ENGINEER or compatible; but with class SOFTWARE_ENGINEER_3 it may be of any type compatible with VOCATION, so that we can achieve the software equivalent of what in real life would be a software engineer who after many years of pretending to be an engineer finally sheds that part of his personality in favor of something that he deems more genuinely representative of his work. This then is our first criterion: --------------------------------------------------------------------------- Rule of change Do not use inheritance to describe a perceived "is-a" relation if the corresponding object components may have to be changed at run time. Inheritance should only be used if the corresponding inter-object relation is permanent. --------------------------------------------------------------------------- In such cases you should use the client relation. Note that the really interesting case is the one illustrated by SOFTWARE_ENGINEER_3. With SOFTWARE_ENGINEER_2 you can only replace the engineer component with another component of the same type; a similar effect could in principle be obtained with inheritance, albeit more awkwardly, through a procedure that changes all the engineer-related fields of the object. (This trick does not, however, permit the replacement to be an instance of a proper descendant of the original; it has to be a direct instance of ENGINEER.) With SOFTWARE_ENGINEER_3 the attribute is of a more general nature, representing objects of many possible types; inheritance is totally appropriate if you foresee the need for such general component changes. The polymorphism ruleNow for a criterion that will
require inheritance and exclude client. That criterion is simple: polymorphic
uses. In our study of inheritance we have seen that with a declaration of the
form x denotes at run time (assuming class C is not expanded) a potentially polymorphic reference; that is to say, x may become attached to direct instances not just of C but of any proper descendants of C. This property is of course a key contribution to the power and flexibility of the object-oriented method, especially through its corollary, the possibility of defining polymorphic data structures, such as a LIST [C] which may contains instances of any of C's descendants. In our example, this means that with the SOFTWARE_ENGINEER_1 solution --- the form of the class which inherits from ENGINEER --- we can have an entity eng: ENGINEER which may become attached at run time to an object of type SOFTWARE_ENGINEER_1. Or we can have a list of engineers, or a database of engineers, which includes a few mechanical engineers, a few chemical engineers, and a few software engineers as well. A reminder on methodology: the use of non-software words is a good help for understanding the concepts, but we should not let ourselves get carried away by such anthropormorphic examples; the objects of interest are software objects. So although we may loosely understand the words "a software engineer" for what they say, they actually denote an instance of SOFTWARE_ENGINEER_1, that is to say a software object somehow modeling a real person. Such polymorphic effects require inheritance: with SOFTWARE_ENGINEER_2 or SOFTWARE_ENGINEER_3 there is no way an entity or data structure of type ENGINEER can denote "software engineer" objects. These observations --- which are not, of course, specific to the example --- yield a general principle: ------------------------------------------------------------------------------- Polymorphism rule Inheritance is appropriate to describe a perceived "is-a" relation if entities or data structure components of the more general type may need to become attached to objects of the more specialized type. ------------------------------------------------------------------------------- SummaryAlthough it brings no new concept, the following rule will be convenient as a summary of this discussion of criteria for and against inheritance. ------------------------------------------------------------------------------ Rules for choosing between client and inheritance In deciding how to express the dependency of a class B on a class A, apply the following criteria: CH1 If every instance of B initially has a component of type A, but that component may need to be replaced at run time by an object of a different type, make B a client of A. CH2 If there is a need for entities of type A to denote objects of type B, or for polymorphic structures containing objects of type A of which some may be of type B, make B an heir of A. ------------------------------------------------------------------------------
|