16 Inheritance techniques omh lst two chapters we havesakeyn in the object-oriented approach to reusability and extendibility.To complete its study we must explore a few more facilities-something of a mixed bag,but all showing striking consequences of the beauty of the basic ideas: How the inheritance mechanism relates to assertions and Design by Contract. The global inheritance structure,where all classes fit. Frozen features:when the Open-Closed principle does not apply. Constrained genericity:how to put requirements on generic parameters. Assignment attempt:how to force a type-safely When and how to change type properties in a redeclaration. The mechanism of anchored declaration,avoiding redeclaration avalanche The tumultuous relationship between inheritance and information hiding. Two later chapters will pursue inheritance-related topics:the review of typing issues in chapter 17,and a detailed methodological discussion of how to use inheritance (and how not to misuse it)in chapter 24. Most of the following sections proceed in the same way:examining a consequence of the inheritance ideas of the last two chapters;discovering that it raises a challenge or an apparent dilemma;analyzing the problem in more depth;and deducing the solution.The key step is usually the next-to-last one:by taking the time to pose the problem carefully, we will often be led directly to the answer. 16.1 INHERITANCE AND ASSERTIONS Because of its very power,inheritance could be dangerous.Were it not for the assertion mechanism,class developers could use redeclaration and dynamic binding to change the semantics of operations treacherously,without much possibility of client control.But assertions will do more:they will give us deeper insights into the nature of inheritance.It is in fact not an exaggeration to state that only through the principles of Design by Contract can one finally understand what inheritance is really about
16 Inheritance techniques From the last two chapters we have learned to appreciate inheritance as a key ingredient in the object-oriented approach to reusability and extendibility. To complete its study we must explore a few more facilities — something of a mixed bag, but all showing striking consequences of the beauty of the basic ideas: • How the inheritance mechanism relates to assertions and Design by Contract. • The global inheritance structure, where all classes fit. • Frozen features: when the Open-Closed principle does not apply. • Constrained genericity: how to put requirements on generic parameters. • Assignment attempt: how to force a type — safely. • When and how to change type properties in a redeclaration. • The mechanism of anchored declaration, avoiding redeclaration avalanche. • The tumultuous relationship between inheritance and information hiding. Two later chapters will pursue inheritance-related topics: the review of typing issues in chapter 17, and a detailed methodological discussion of how to use inheritance (and how not to misuse it) in chapter 24. Most of the following sections proceed in the same way: examining a consequence of the inheritance ideas of the last two chapters; discovering that it raises a challenge or an apparent dilemma; analyzing the problem in more depth; and deducing the solution. The key step is usually the next-to-last one: by taking the time to pose the problem carefully, we will often be led directly to the answer. 16.1 INHERITANCE AND ASSERTIONS Because of its very power, inheritance could be dangerous. Were it not for the assertion mechanism, class developers could use redeclaration and dynamic binding to change the semantics of operations treacherously, without much possibility of client control. But assertions will do more: they will give us deeper insights into the nature of inheritance. It is in fact not an exaggeration to state that only through the principles of Design by Contract can one finally understand what inheritance is really about
570 INHERITANCE TECHNIQUES $16.1 The basic rules governing the rapport between inheritance and assertions have already been sketched:in a descendant class,all ancestors'assertions (routine preconditions and postconditions,class invariants)still apply.This section gives the rules more precisely and uses the results obtained to take a new look at inheritance,viewed as subcontracting. Invariants We already encountered the rule for class invariants: Parents'Invariant rule The invariants of all the parents of a class apply to the class itself. The parents'invariants are added to the class's own,"addition"being here a logical and then.(If no invariant is given in a class,it is considered to have True as invariant.) By induction the invariants of all ancestors,direct or indirect,apply. As a consequence,you should not repeat the parents'invariant clauses in the invariant of a class (although such redundancy would be semantically harmless since a and then a is the same thing as a). The flat and flat-short forms of the class will show the complete reconstructed See“FLATTENING invariant,all ancestors'clauses concatenated. THE STRUCTURE”, l5.3.page541. Preconditions and postconditions in the presence of dynamic binding The case of routine preconditions and postconditions is slightly more delicate.The general idea,as noted,is that any redeclaration must satisfy the assertions on the original routine. This is particularly important if that routine was deferred:without such a constraint on possible effectings,attaching a precondition and a postcondition to a deferred routine would be useless or,worse,misleading.But the need is just as bad with redefinitions of effective routines. The exact rule will follow directly from a careful analysis of the consequences of redeclaration,polymorphism and dynamic binding.Let us construct a typical case and deduce the rule from that analysis. Consider a class and one of its routines with a precondition and a postcondition: ris The routine, require the client and the contract ensure B end The figure also shows a client C of 4.The typical way for C to be a client is to include,in one of its routines,a declaration and call of the form
570 INHERITANCE TECHNIQUES §16.1 The basic rules governing the rapport between inheritance and assertions have already been sketched: in a descendant class, all ancestors’ assertions (routine preconditions and postconditions, class invariants) still apply. This section gives the rules more precisely and uses the results obtained to take a new look at inheritance, viewed as subcontracting. Invariants We already encountered the rule for class invariants: The parents’ invariants are added to the class’s own, “addition” being here a logical and then. (If no invariant is given in a class, it is considered to have True as invariant.) By induction the invariants of all ancestors, direct or indirect, apply. As a consequence, you should not repeat the parents’ invariant clauses in the invariant of a class (although such redundancy would be semantically harmless since a and then a is the same thing as a). The flat and flat-short forms of the class will show the complete reconstructed invariant, all ancestors’ clauses concatenated. Preconditions and postconditions in the presence of dynamic binding The case of routine preconditions and postconditions is slightly more delicate. The general idea, as noted, is that any redeclaration must satisfy the assertions on the original routine. This is particularly important if that routine was deferred: without such a constraint on possible effectings, attaching a precondition and a postcondition to a deferred routine would be useless or, worse, misleading. But the need is just as bad with redefinitions of effective routines. The exact rule will follow directly from a careful analysis of the consequences of redeclaration, polymorphism and dynamic binding. Let us construct a typical case and deduce the rule from that analysis. Consider a class and one of its routines with a precondition and a postcondition: The figure also shows a client C of A. The typical way for C to be a client is to include, in one of its routines, a declaration and call of the form Parents’ Invariant rule The invariants of all the parents of a class apply to the class itself. See “FLATTENING THE STRUCTURE”, 15.3, page 541. The routine, the client and the contract A r is require α … ensure β end C
$16.1 INHERITANCE AND ASSERTIONS 571 al:A al.r For simplicity,we ignore any arguments that r may require,and we assume that r is a procedure,although the discussion applies to a function just as well. Of course the call will only be correct if it satisfies the precondition.One way for C to make sure that it observes its part of the contract is to protect the call by a precondition test,writing it (instead of just al.r)as if al.o then al.r check al..βend --i.e.the postcondition holds ..Instructions that may assume al.B... end (As noted in the discussion ofassertions,this is not required:it suffices to guarantee,with or without an if instruction,that o holds before the call.We will assume the if form for simplicity,and ignore any else clause. Having guaranteed the precondition,the client C is entitled to the postcondition on return:after the call,it may expect that a/.B will hold. All this is the basics of Design by Contract:the client must ensure the precondition on calling the routine and,as a recompense,may count on the postcondition being satisfied when the routine exits. What happens when inheritance enters the picture? ris The routine, require the client,the +。+ contract and ensure the descendant B end r+is require Y ensure end Assume that a new class A'inherits from A and redeclares r.How,if at all,can it change the precondition o into a new oney and the postcondition B into a new one 8? To decide the answer,consider the plight of the client.In the call al.r the target al may now,out of polymorphism,be of type 4'rather than just 4.But C does not know about this!The only declaration for a/may still be the original one: al:A
§16.1 INHERITANCE AND ASSERTIONS 571 a1: A … a1 ● r For simplicity, we ignore any arguments that r may require, and we assume that r is a procedure, although the discussion applies to a function just as well. Of course the call will only be correct if it satisfies the precondition. One way for C to make sure that it observes its part of the contract is to protect the call by a precondition test, writing it (instead of just a1 ● r) as if a1 ● α then a1 ● r check a1 ● β end -- i.e. the postcondition holds … Instructions that may assume a1 ● β … end (As noted in the discussion of assertions, this is not required: it suffices to guarantee, with or without an if instruction, that α holds before the call. We will assume the if form for simplicity, and ignore any else clause.) Having guaranteed the precondition, the client C is entitled to the postcondition on return: after the call, it may expect that a1 ● β will hold. All this is the basics of Design by Contract: the client must ensure the precondition on calling the routine and, as a recompense, may count on the postcondition being satisfied when the routine exits. What happens when inheritance enters the picture? Assume that a new class A' inherits from A and redeclares r. How, if at all, can it change the precondition α into a new one γ and the postcondition β into a new one δ? To decide the answer, consider the plight of the client. In the call a1 ● r the target a1 may now, out of polymorphism, be of type A' rather than just A. But C does not know about this! The only declaration for a1 may still be the original one: a1: A The routine, the client, the contract and the descendant A r is require α … ensure β end C A' r++ is require γ … ensure δ end
572 INHERITANCE TECHNIQUES $16.1 which names 4,not 4.In fact C may well use 4'without its author ever knowing about the existence of such a class;the call to rmay for example be in a routine of C of the form some_routine of C(al:A)is do al.r… end Then a call to some routine of C from another class may use an actual argument of type A,even though the text of C contains no mention of class 4.Dynam ic binding means that the call to r will in that case use the redefined A'version. So we can have a situation where C is only a client ofA but in fact will at run time use the d'version of some features.(We could say that C is a"dynamic client"of 4'even though its text does not show it.) What does this mean for C?The answer,unless we do something,is:trouble.C can be an honest client,observing its part of the deal,and still be cheated on the result.In if al.a then al.r end ifa/is polymorphically attached to an object of type 4,the instruction calls a routine that expects yand guarantees 6,whereas the client has been told to satisfy o and expect B.So we have a potential discrepancy between the client's and supplier's views of the contract. How to cheat clients To understand how to satisfy the clients'expectations,we have to play devil's advocate and imagine for a second how we could fool them.It is all for a good cause,of course(as with a crime unit that tries to emulate criminals'thinking the better to fight it,or a computer security expert who studies the techniques of computer intruders). If we,the supplier,wanted to cheat our poor,honest C client,who guarantees o and expects B,how would we proceed?There are actually two ways to evil: We could require more than the original precondition a.With a stronger precondition,we allow ourselves to exclude(that is to say,not to guarantee any specific result)for cases that,according to the original specification,were perfectly acceptable. Remember the point emphasized repeatedly in the discussion of Design by Contract:making a precondition stronger facilitates the task of the supplier ("the client is more often wrong"),as illustrated by the extreme case of precondition false ("the client is al ways wrong"). We could ensure less than the original postcondition B.With a weaker postcondition, we allow ourselves to produce less than what the original specification promised. As we saw,an assertion is said to be stronger than another if it logically implies it, and is different;for example,x>=5 is stronger thanx>=0.If A is stronger than B,B is said to be weaker than 4
572 INHERITANCE TECHNIQUES §16.1 which names A, not A'. In fact C may well use A' without its author ever knowing about the existence of such a class; the call to r may for example be in a routine of C of the form some_routine_of_C (a1: A) is do …; a1 ● r; … end Then a call to some_routine_of_C from another class may use an actual argument of type A', even though the text of C contains no mention of class A'. Dynamic binding means that the call to r will in that case use the redefined A' version. So we can have a situation where C is only a client of A but in fact will at run time use the A' version of some features. (We could say that C is a “dynamic client” of A' even though its text does not show it.) What does this mean for C? The answer, unless we do something, is: trouble. C can be an honest client, observing its part of the deal, and still be cheated on the result. In if a1 ● α then a1 ● r end if a1 is polymorphically attached to an object of type A', the instruction calls a routine that expects γ and guarantees δ, whereas the client has been told to satisfy α and expect β. So we have a potential discrepancy between the client’s and supplier’s views of the contract. How to cheat clients To understand how to satisfy the clients’ expectations, we have to play devil’s advocate and imagine for a second how we could fool them. It is all for a good cause, of course (as with a crime unit that tries to emulate criminals’ thinking the better to fight it, or a computer security expert who studies the techniques of computer intruders). If we, the supplier, wanted to cheat our poor, honest C client, who guarantees α and expects β, how would we proceed? There are actually two ways to evil: • We could require more than the original precondition α. With a stronger precondition, we allow ourselves to exclude (that is to say, not to guarantee any specific result) for cases that, according to the original specification, were perfectly acceptable. Remember the point emphasized repeatedly in the discussion of Design by Contract: making a precondition stronger facilitates the task of the supplier (“the client is more often wrong”), as illustrated by the extreme case of precondition false (“the client is always wrong”). • We could ensure less than the original postcondition β. With a weaker postcondition, we allow ourselves to produce less than what the original specification promised. As we saw, an assertion is said to be stronger than another if it logically implies it, and is different; for example, x >= 5 is stronger than x >= 0. If A is stronger than B, B is said to be weaker than A
$16.1 INHERITANCE AND ASSERTIONS 573 How to be honest From understanding how to cheat we deduce how to be honest.When redeclaring a routine,we may keep the original assertions,but we may also: Replace the precondition by a weaker one. Replace the postcondition by a stronger one. The first case means being more generous than the original-accepting more cases This can cause no harm to a client that satisfies the original precondition before the call The second case means producing more than what was promised;this can cause no harm to a client call that relies on the original postcondition being satisfied after the call. Hence the basic rule: Assertion Redeclaration rule (1) A routine redeclaration may only replace the original precondition by one equal or weaker,and the original postcondition by one equal or stronger. The rule expresses that the new version must accept all calls that were acceptable to the original,and must guarantee at least as much as was guaranteed by the original.It may but does not have to-accept more cases,or provide stronger guarantees. As its name indicates,this rule applies to both forms of redeclaration:redefinitions and effectings.The second case is particularly important,since it allows you to take seriously the assertions that may be attached to a deferred feature;these assertions will be binding on all effective versions in descendants. For a more rigorous The assertions of a routine,deferred or effective,specify the essential semantics of definition see“A mathematical note” the routine,applicable not only to the routine itself but to any redeclaration in descendants. page 580 More precisely,they specify a range of acceptable behaviors for the routine and its eventual redeclarations.A redeclaration may specialize this range,but not violate it. A consequence for the class author is the need to be careful,when writing the assertions of an effective routine,not to overspecify.The assertions must characterize the intent of the routine-its abstract semantics -not the properties of the original implementation.If you overspecify,you may be closing off the possibility for a future descendant to provide a different implementation. An example Assume I write a class M4TRIY implementing linear algebra operations.Among the features I offer to my clients is a matrix inversion routine.It is actually a combination of a command and two queries:procedure ineri inverts the matrix,and sets attribute inverse to the value of the inverse matrix,as well as a boolean attribute imerse valid.The value of inverse is meaningful if and only if inverse valid is true;otherwise the inversion has failed because the matrix was singular.For this discussion we can ignore the singularity case
§16.1 INHERITANCE AND ASSERTIONS 573 How to be honest From understanding how to cheat we deduce how to be honest. When redeclaring a routine, we may keep the original assertions, but we may also: • Replace the precondition by a weaker one. • Replace the postcondition by a stronger one. The first case means being more generous than the original — accepting more cases. This can cause no harm to a client that satisfies the original precondition before the call. The second case means producing more than what was promised; this can cause no harm to a client call that relies on the original postcondition being satisfied after the call. Hence the basic rule: The rule expresses that the new version must accept all calls that were acceptable to the original, and must guarantee at least as much as was guaranteed by the original. It may — but does not have to — accept more cases, or provide stronger guarantees. As its name indicates, this rule applies to both forms of redeclaration: redefinitions and effectings. The second case is particularly important, since it allows you to take seriously the assertions that may be attached to a deferred feature; these assertions will be binding on all effective versions in descendants. The assertions of a routine, deferred or effective, specify the essential semantics of the routine, applicable not only to the routine itself but to any redeclaration in descendants. More precisely, they specify a range of acceptable behaviors for the routine and its eventual redeclarations. A redeclaration may specialize this range, but not violate it. A consequence for the class author is the need to be careful, when writing the assertions of an effective routine, not to overspecify. The assertions must characterize the intent of the routine — its abstract semantics —, not the properties of the original implementation. If you overspecify, you may be closing off the possibility for a future descendant to provide a different implementation. An example Assume I write a class MATRIX implementing linear algebra operations. Among the features I offer to my clients is a matrix inversion routine. It is actually a combination of a command and two queries: procedure invert inverts the matrix, and sets attribute inverse to the value of the inverse matrix, as well as a boolean attribute inverse_valid. The value of inverse is meaningful if and only if inverse_valid is true; otherwise the inversion has failed because the matrix was singular. For this discussion we can ignore the singularity case. Assertion Redeclaration rule (1) A routine redeclaration may only replace the original precondition by one equal or weaker, and the original postcondition by one equal or stronger. For a more rigorous definition see “A mathematical note”, page 580