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
574 INHERITANCE TECHNIQUES $16.1 Of course I can only compute an approximation of the inverse of a matrix.I am prepared to guarantee a certain precision of the result,but since I am not very good at numerical analysis,I shall only accept requests for a precision not better than 10-6.The resulting routine will look like this: invert (epsilon:REAL)is --Inverse of current matrix,with precision epsilon require epsilon>=10A(-6) do “Computation of inverse” ensure ((Current inverse)One)=10(-20) redefinition.See next. ensure ((Current inverse)One)10(-20)is weaker than (that is to say,implied by)the original epsilon >=10^(-6);and the new postcondition is stronger than the original. This is how it should be.A client of the original MATRIX may be requesting a matrix inversion but,through dynamic binding,actually calling the NEW MATRIX variant.The client could contain a routine some client routine (ml:MATRIX;precision:REAL)is do ...ml.invert (precision),.. --May use either the MATRIX or the NEW MATRIX version end to which one of its own clients passes a first argument of type NEW MATRIY
574 INHERITANCE TECHNIQUES §16.1 Of course I can only compute an approximation of the inverse of a matrix. I am prepared to guarantee a certain precision of the result, but since I am not very good at numerical analysis, I shall only accept requests for a precision not better than 10–6. The resulting routine will look like this: invert (epsilon: REAL) is -- Inverse of current matrix, with precision epsilon require epsilon >= 10 ^ (–6) do “Computation of inverse” ensure ((Current ∗ inverse) |–| One) = 10 ^ (–20) … ensure ((Current ∗ inverse) |–| One) = 10 ^ (–20) is weaker than (that is to say, implied by) the original epsilon >= 10 ^ (–6); and the new postcondition is stronger than the original. This is how it should be. A client of the original MATRIX may be requesting a matrix inversion but, through dynamic binding, actually calling the NEW_MATRIX variant. The client could contain a routine some_client_routine (m1: MATRIX; precision: REAL) is do … ; m1 ● invert (precision); … -- May use either the MATRIX or the NEW_MATRIX version end to which one of its own clients passes a first argument of type NEW_MATRIX. Warning: syntactically not valid as a redefinition. See next
$16.1 INHERITANCE AND ASSERTIONS 575 NEW MATRIY must be able to accept and handle correctly any call that MATRIY would accept.If we made the precondition of the new invert stronger than the original (as in epsilon>=(-5)),calls which are correct for MATRIX would now be incorrect;if we made the postcondition weaker,the result returned would not be as good as guaranteed by MATRIY.By using a weaker precondition and a stronger postcondition we correctly treat all calls from clients of MATRIY,while offering a better deal to our own clients. Cutting out the middleman The last comment points to an interesting consequence of the Assertion Redeclaration rule.In our general scheme ris The routine, require the client and 0 the sub- ensure contractor B end r++is require Y ensure 6 end the assertions of the redeclared version,y and 8,if different from a and B,are more favorable to the clients,in the sense explained earlier (weaker precondition,stronger postcondition).But a client of 4 which uses 4'through polymorphism and dynamic binding cannot make good use of this improved contract,since its only contract is with 4. Only by becoming a direct client of'(the shaded link with a question mark on the last figure)can you take advantage of the new contract,as in al:A' if al.y then al.r end check al.δend --i.e.the postcondition holds But then of course you have specialized al to be of type A,not the general 4;you have lost the polymorphic generality of going through 4. The tradeoff is clear.A client of MATRIY must satisfy the original (stronger) precondition,and may only expect the original (weaker)postcondition;even if its request gets served dynamically by NEW MATRIX it has no way of benefiting from the broader tolerance of inputs and tighter precision of results.To get this improved specification it must declare the matrix to be of type NEW MATRIY,thereby losing access to other implementations represented by descendants of M4TRIY that are not also descendants of NEW MATRIY
§16.1 INHERITANCE AND ASSERTIONS 575 NEW_MATRIX must be able to accept and handle correctly any call that MATRIX would accept. If we made the precondition of the new invert stronger than the original (as in epsilon >= ^ (–5)), calls which are correct for MATRIX would now be incorrect; if we made the postcondition weaker, the result returned would not be as good as guaranteed by MATRIX. By using a weaker precondition and a stronger postcondition we correctly treat all calls from clients of MATRIX, while offering a better deal to our own clients. Cutting out the middleman The last comment points to an interesting consequence of the Assertion Redeclaration rule. In our general scheme the assertions of the redeclared version, γ and δ, if different from α and β, are more favorable to the clients, in the sense explained earlier (weaker precondition, stronger postcondition). But a client of A which uses A' through polymorphism and dynamic binding cannot make good use of this improved contract, since its only contract is with A. Only by becoming a direct client of A' (the shaded link with a question mark on the last figure) can you take advantage of the new contract, as in a1: A' … if a1 ● γ then a1 ● r end check a1 ● δ end -- i.e. the postcondition holds But then of course you have specialized a1 to be of type A', not the general A; you have lost the polymorphic generality of going through A. The tradeoff is clear. A client of MATRIX must satisfy the original (stronger) precondition, and may only expect the original (weaker) postcondition; even if its request gets served dynamically by NEW_MATRIX it has no way of benefiting from the broader tolerance of inputs and tighter precision of results. To get this improved specification it must declare the matrix to be of type NEW_MATRIX, thereby losing access to other implementations represented by descendants of MATRIX that are not also descendants of NEW_MATRIX. The routine, the client and the subcontractor A r is require α … ensure β end C A' r++ is require γ … ensure δ end ?
576 INHERITANCE TECHNIQUES $16.1 Subcontracting The Assertion Redeclaration rule fits nicely in the Design by Contract theory introduced in the chapter bearing that title. We saw that the assertions of a routine describe the contract associated with that routine:the client is bound by the precondition and entitled to the postcondition,and conversely for the class implementer. Inheritance,with redeclaration and dynamic binding,means subcontracting.When you have accepted a contract,you do not necessarily want to carry it out yourself. Sometimes you know of somebody else who can do it cheaper and perhaps better.This is exactly what happens when a client requests a routine from M4TR/Y but,through dynamic binding,may actually call at run time a version redefined in a proper descendant.Here "cheaper"refers to routine redefinition for more efficiency,as in the rectangle perimeter example of an earlier chapter,and "better"to improved assertions in the sense just seen. The Assertion Redeclaration rule simply states that if you are an honest subcontractor and accept a contract,you must be willing to do the job originally requested, or better than the requested job,but not less. The scheme described in the last section-declaring a/of type 4'to benefit from the improved contract-is similar to the behavior of a customer who tries to get a better deal by bypassing his contractor to work directly with the contractor's own subcontractor In the Design by Contract view,class invariants are general constraints applying to both contractors and clients.The parents'invariant rule expresses that all such constraints are transmitted to subcontractors. It is only with assertions,and with the two rules just seen,that inheritance takes on its full meaning for object-oriented design.The contracting-subcontracting metaphor is a powerful analogy to guide the development of correct object-oriented software;certainly one of the central deas. Abstract preconditions The rule on weakening preconditions may appear too restrictive in the case of an heir that restricts the abstraction provided by its parent.Fortunately,there is an easy workaround, consistent with the theory. A typical example arises if you want to make a class BOUNDED STACK inherit from a general STACK class.In BOUNDED_STACK the procedure for pushing an element onto the stack,put,has a precondition,which requires count <capacity,where count is the current number of stack elements and capacity is the physically available size. For the general notion of STACK,however,there is no notion of capacity.So it seems we need to strengthen the precondition when we move down to BOUNDED STACK.How do we build this inheritance structure without violating the Assertion Redeclaration rule? The answer is straightforward if we take a closer look at client needs.What needs to be kept or weakened is not necessarily the concrete precondition as implemented by the
576 INHERITANCE TECHNIQUES §16.1 Subcontracting The Assertion Redeclaration rule fits nicely in the Design by Contract theory introduced in the chapter bearing that title. We saw that the assertions of a routine describe the contract associated with that routine: the client is bound by the precondition and entitled to the postcondition, and conversely for the class implementer. Inheritance, with redeclaration and dynamic binding, means subcontracting. When you have accepted a contract, you do not necessarily want to carry it out yourself. Sometimes you know of somebody else who can do it cheaper and perhaps better. This is exactly what happens when a client requests a routine from MATRIX but, through dynamic binding, may actually call at run time a version redefined in a proper descendant. Here “cheaper” refers to routine redefinition for more efficiency, as in the rectangle perimeter example of an earlier chapter, and “better” to improved assertions in the sense just seen. The Assertion Redeclaration rule simply states that if you are an honest subcontractor and accept a contract, you must be willing to do the job originally requested, or better than the requested job, but not less. The scheme described in the last section — declaring a1 of type A' to benefit from the improved contract — is similar to the behavior of a customer who tries to get a better deal by bypassing his contractor to work directly with the contractor’s own subcontractor In the Design by Contract view, class invariants are general constraints applying to both contractors and clients. The parents’ invariant rule expresses that all such constraints are transmitted to subcontractors. It is only with assertions, and with the two rules just seen, that inheritance takes on its full meaning for object-oriented design. The contracting-subcontracting metaphor is a powerful analogy to guide the development of correct object-oriented software; certainly one of the central deas. Abstract preconditions The rule on weakening preconditions may appear too restrictive in the case of an heir that restricts the abstraction provided by its parent. Fortunately, there is an easy workaround, consistent with the theory. A typical example arises if you want to make a class BOUNDED_STACK inherit from a general STACK class. In BOUNDED_STACK the procedure for pushing an element onto the stack, put, has a precondition, which requires count <= capacity, where count is the current number of stack elements and capacity is the physically available size. For the general notion of STACK, however, there is no notion of capacity. So it seems we need to strengthen the precondition when we move down to BOUNDED_ STACK. How do we build this inheritance structure without violating the Assertion Redeclaration rule? The answer is straightforward if we take a closer look at client needs. What needs to be kept or weakened is not necessarily the concrete precondition as implemented by the
$16.1 INHERITANCE AND ASSERTIONS 577 supplier(which is the supplier's business),but the precondition as seen by the client. Assume that we write put in STACK as put (x:G)is --Push x on top. require not full deferred ensure 4+4 end with a function/il/defined always to return false,so that by default stacks are never full: full:BOOLEAN is --Is representation full? --(Default:no) do Result:=False end Then it suffices in BOUNDED STACK to redefine full: full:BOOLEAN is --Is representation full? --(Answer:if and only if number of items is capacity) do Result:=(count capacity)end A precondition such as not ful/,based on a property that is redefinable in descendants,is called an abstract precondition. This use of abstract preconditions to satisfy the Assertion Redeclaration rule may appear to be cheating,but it is not:although the concrete precondition is in fact being strengthened,the abstract precondition remains the same.What counts is not how the assertion is implemented,but how it is presented to the clients as part of the class interface (the short or flat-short form).A protected call of the form if not s.full then s.put (a)end will be valid regardless of the kind of STACK attached to s. There is,however,a valid criticism of this approach:it goes against the Open-Closed principle.We must foresee,at the STA4CK level,that some stacks will have a bounded capacity;if we have not exerted such foresight,we must go back to STACK and change its interface.But this is inevitable.Of the following two properties A bounded stack is a stack. It is always possible to add an element to a stack. one must go.If we want the first property,permitting BOUNDED STACK to inherit from STACK,we must accept that the general notion of stack includes the provision that a put operation is not always possible,expressed abstractly by the presence of the query fill
§16.1 INHERITANCE AND ASSERTIONS 577 supplier (which is the supplier’s business), but the precondition as seen by the client. Assume that we write put in STACK as put (x: G) is -- Push x on top. require not full deferred ensure … end with a function full defined always to return false, so that by default stacks are never full: full: BOOLEAN is -- Is representation full? -- (Default: no) do Result := False end Then it suffices in BOUNDED_STACK to redefine full: full: BOOLEAN is -- Is representation full? -- (Answer: if and only if number of items is capacity) do Result := (count = capacity) end A precondition such as not full, based on a property that is redefinable in descendants, is called an abstract precondition. This use of abstract preconditions to satisfy the Assertion Redeclaration rule may appear to be cheating, but it is not: although the concrete precondition is in fact being strengthened, the abstract precondition remains the same. What counts is not how the assertion is implemented, but how it is presented to the clients as part of the class interface (the short or flat-short form). A protected call of the form if not s ● full then s ● put (a) end will be valid regardless of the kind of STACK attached to s. There is, however, a valid criticism of this approach: it goes against the Open-Closed principle. We must foresee, at the STACK level, that some stacks will have a bounded capacity; if we have not exerted such foresight, we must go back to STACK and change its interface. But this is inevitable. Of the following two properties • A bounded stack is a stack. • It is always possible to add an element to a stack. one must go. If we want the first property, permitting BOUNDED_STACK to inherit from STACK, we must accept that the general notion of stack includes the provision that a put operation is not always possible, expressed abstractly by the presence of the query full
578 INHERITANCE TECHNIQUES $16.1 It would clearly be a mistake,in class STACK,to include Result=False as a postcondition for fil/or(equivalently but following the recommended style)an invariant clause not full.This would be a case of overspecification as mentioned earlier,hampering the descendants'freedom to adapt the feature. The language rule The Assertion Redeclaration rule as given so far is a conceptual guideline.How do we transform it into a safe,checkable language rule? We should in principle rely on a logical analysis of the old and new assertions,to verify that the old precondition logically implies the new one,and that the new postcondition implies the old one.Unfortunately,such a goal would require a sophisticated theorem prover which,if at all feasible,is still far too difficult (in spite of decades of research in artificial intelligence)to be integrated routinely among the checks performed by a compiler. Fortunately a low-tech solution is available.We can enforce the rule through a simple language convention,based on the observation that for any assertions o and B: o implies o or y,regardless of what y is. ·Bandδimpliesβ,regardless of whatδis. So to be sure that a new precondition is weaker than or equal to an original o,it suffices to accept it only if it is of the form a or y;and to be sure that a new postcondition is stronger than or equal to an original B,it suffices to accept it only if it is of the form B and 8.Hence the language rule implementing the original methodological rule: Assertion Redeclaration rule (2) In the redeclared version of a routine,it is not permitted to use a require or ensure clause.Instead you may: Use a clause introduced by require else,to be or-ed with the original precondition. Use a clause introduced by ensure then,to be and-ed with the original postcondition. In the absence of such a clause,the original assertion is retained. Note that the operators used for or-ing and for and-ing are the non-strict boolean See“Non-strict operators or else and and then rather than plain or and and,although in most cases the boolean operators". difference is irrelevant. page 454 Sometimes the resulting assertions will be more complicated than strictly necessary. For example in our matrix routine,where the original read
578 INHERITANCE TECHNIQUES §16.1 It would clearly be a mistake, in class STACK, to include Result = False as a postcondition for full or (equivalently but following the recommended style) an invariant clause not full. This would be a case of overspecification as mentioned earlier, hampering the descendants’ freedom to adapt the feature. The language rule The Assertion Redeclaration rule as given so far is a conceptual guideline. How do we transform it into a safe, checkable language rule? We should in principle rely on a logical analysis of the old and new assertions, to verify that the old precondition logically implies the new one, and that the new postcondition implies the old one. Unfortunately, such a goal would require a sophisticated theorem prover which, if at all feasible, is still far too difficult (in spite of decades of research in artificial intelligence) to be integrated routinely among the checks performed by a compiler. Fortunately a low-tech solution is available. We can enforce the rule through a simple language convention, based on the observation that for any assertions α and β: • α implies α or γ, regardless of what γ is. • β and δ implies β, regardless of what δ is. So to be sure that a new precondition is weaker than or equal to an original α, it suffices to accept it only if it is of the form α or γ; and to be sure that a new postcondition is stronger than or equal to an original β, it suffices to accept it only if it is of the form β and δ. Hence the language rule implementing the original methodological rule: Note that the operators used for or-ing and for and-ing are the non-strict boolean operators or else and and then rather than plain or and and, although in most cases the difference is irrelevant. Sometimes the resulting assertions will be more complicated than strictly necessary. For example in our matrix routine, where the original read Assertion Redeclaration rule (2) In the redeclared version of a routine, it is not permitted to use a require or ensure clause. Instead you may: • Use a clause introduced by require else, to be or-ed with the original precondition. • Use a clause introduced by ensure then, to be and-ed with the original postcondition. In the absence of such a clause, the original assertion is retained. See “Non-strict boolean operators”, page 454