Previous | Table of Contents | Next |
In all such cases, multiple inheritance provides the answer.
Multiple inheritance can cause name clashes: Two parents can include a feature with the same name. This would conflict with the ban on name overloading within a classthe rule that no two features of a class may have the same name. Eiffel provides a simple way to remove the name clash at the point of inheritance through the rename subclause, as in
indexing description:Sequential finite lists implemented as arrays class ARRAYED_LIST [G] inherit LIST [G] ARRAY [G] rename count as capacity, item as array_item end feature ... end -- class ARRAYED_LIST
Here both LIST and ARRAY have features called count and item. To make the new class valid, we give new names to the features inherited from ARRAY, which are known within ARRAYED_LIST as capacity and array_item. Of course, we could have renamed the LIST versions instead or renamed along both inheritance branches.
Every feature of a class has a final name. For a feature introduced in the class itself (immediate feature), it is the name appearing in the declaration; for an inherited feature that is not renamed, it is the features name in the parent; for a renamed feature, it is the name resulting from the renaming. This definition yields a precise statement of the rule against in-class overloading:
Final name rule:
Two different features of a class may not have the same final name.
It is interesting to compare renaming and redefinition. The important distinction is between features and feature names. Renaming keeps a feature but changes its name. Redefinition keeps the name but changes the feature. In some cases, it is, of course, appropriate to do both.
Renaming is interesting even in the absence of name clashes. A class may inherit from a parent a feature that it finds useful for its purposes, but whose name, appropriate for the context of the parent, is not consistent with the context of the heir. This is the case with ARRAYs feature count in the last example. The feature that defines the number of items in an arraythe total number of available entriesbecomes, for an arrayed list, the maximum number of list items. The truly interesting indication of the number of items is the count of how many items have been inserted in the list, as given by feature count from LIST. Even if we did not have a name clash because of the two inherited count features we should rename ARRAYs count as capacity to maintain the consistency of the local feature terminology.
The rename subclause appears before all the other feature adaptation subclausesredefine already seen and the remaining ones export, undefine, and selectbecause an inherited feature that has been renamed sheds its earlier identity once and for all. Within the class, and to its own clients and descendants, it is known solely through the new name. The original name has simply disappeared from the namespace. This is essential to the view of classes presented earlier: self-contained, consistent abstractions prepared carefully for the greatest enjoyment of clients and descendants.
A proper understanding of inheritance requires looking at the mechanism in the framework of design by contract, where it appears as a form of subcontracting.
The first rule is that invariants accumulate down an inheritance structure:
Invariant accumulation rule:
The invariants of all the parents of a class apply to the class itself.
The invariant of a class is automatically considered to includein the sense of logical andthe invariants of all its parents. This is a consequence of the view of inheritance as an is relation: If we may consider every instance of B as an instance of A, then every consistency constraint on instances of A must also apply to instances of B.
Next, we consider routine preconditions and postconditions. The rule here follows from an examination of what contracts mean in the presence of polymorphism and dynamic binding.
Consider a parent A and a proper descendant B (a direct heir on Figure 9.11), which redefines a routine r inherited from A.
FIGURE 9.11. Client, parent, and heir.
As a result of dynamic binding, a call a1.r from a client C may be serviced not by As version of r but by Bs version if a1, although declared of type A, becomes at runtime attached to an instance of B. This shows the combination of inheritance, redefinition, polymorphism, and dynamic binding as providing a form of subcontracting; A subcontracts certain calls to B.
The problem is to keep subcontractors honest. Assuming preconditions and postconditions as shown on Figure 9.11, a call in C of the form
if a1.pre then a1.r end
or just a1.q; a1.r, where the postcondition of q implies the precondition pre of r, satisfies the terms of the contract and hence is entitled to be handled correctlyto terminate in a state satisfying a1.post. If we let the subcontractor B redefine the assertions to arbitrary pre and post, this is not necessarily the case: pre could be stronger than pre, enabling B not to process correctly certain calls that are correct from As perspective, and post could be weaker than post, enabling B to do less of a job than advertised for r in the short form of A, the only official reference for authors of client classes such as C. (An assertion p is stronger than or equal to an assertion q if p implies q in the sense of boolean implication.)
The rule, then, is that for the redefinition to be correct, the new precondition pre must be weaker than or equal to the original pre, and the new postcondition post must be stronger than or equal to the original post.
Previous | Table of Contents | Next |