Previous | Table of Contents | Next |
The first form of feature adaptation is the ability to change the implementation of an inherited feature. Assume a class SAVINGS_ACCOUNT that specializes the notion of account. It is probably appropriate to define it as an heir to class ACCOUNT, to benefit from all the features of ACCOUNT still applicable to savings accounts, and express the conceptual relationship that every savings account is an account (apart from its own specific properties). However, we may need to produce a different effect for procedure deposit so that besides recording the deposit and updating the balance, it also updates the interest, for instance.
This example is typical of the form of reuse promoted by inheritance and crucial to effective reusability in software: the case of reuse with adaptation. Traditional forms of reuse are all-or-nothing: Either you take a component exactly as it is, or you build your own. Inheritance gets us out of this reuse or redo dilemma by allowing us to reuse and redo. The mechanism is feature redefinition:
indexing description: Savings accounts class SAVINGS_ACCOUNT inherit ACCOUNT redefine deposit end feature -- Element change deposit (sum: INTEGER) is -- Add sum to account. do ... New implementation (see below) ... end ... Other features ... end -- class SAVINGS_ACCOUNT
Without the redefine subclause, the declaration of deposit would be invalid, yielding two features of the same name, the inherited one and the new one. The subclause makes this valid by specifying that the new declaration overrides the old one.
In a redefinition, the original versionsuch as the ACCOUNT implementation of deposit in this exampleis called the precursor of the new version. It is common for a redefinition to rely on the precursors algorithm and add some other actions; the reserved word Precursor helps achieve this goal simply. Permitted only in a routine redefinition, it denotes the parent routine being redefined. Here the body of the new deposit could be of the form
Precursor (sum) -- Apply algorithm of ACCOUNTs version of deposit ... Instructions to update the interest ...
Besides changing the implementation of a routine, a redefinition can turn an argument-less function into an attribute; for example, a proper descendant of ACCOUNT could redefine deposits_count, originally a function, as an attribute. The principle of uniform access (section 9.6.2) guarantees that the redefinition makes no change for clients, which continues to use the feature under the form acc.deposits_count.
The inheritance mechanism is relevant to both roles of classes: module and type. Its application as a mechanism to reuse, adapt, and extend features from one class to another, as just seen, covers the module role. But inheritance is also a subtyping mechanism. To say that D is an heir of A, or more generally, a descendant of A, is to express that instances of D can be viewed as instances of A.
The mechanism that supports this idea is polymorphic assignment. In an assignment x := y, the types of y do not, thanks to inheritance, have to be identical; the rule is that the type of y must simply conform to another. A class D conforms to a class A if and only if it is a descendant (which of course includes the case in which A and D are the same class); if these classes are generic, conformance of D [U] to C [T] requires in addition that type U conform to type T (through the recursive application of the same rules).
With the inheritance relations suggested earlier, the declarations
acc: ACCOUNT; sav: SAVINGS_ACCOUNT
make it valid to write the assignment
acc := sav
which assigns to acc a reference attached (if not void) to a direct instance of type SAVINGS_ACCOUNT, not ACCOUNT.
Such an assignment, where the source and target types are different, is said to be polymorphic. An entity such as acc, which as a result of such assignments may become attached at runtime to objects of types other than the one declared for it, is itself called a polymorphic entity.
For polymorphism to respect the reliability requirements of Eiffel, it must be controlled by the type system and enable static type checking. We certainly do not want an entity of type ACCOUNT to become attached to an object of type DEPOSIT. The second typing rule:
Type conformance rule:
An assignment x := y, or the use of y as actual argument corresponding to the formal argument x in a routine call, is only valid if the type of y conforms to the type of x.
The second case is that of a call such as target.routine (..., y, ...) where the corresponding routine declaration is of the form routine (..., x: SOME_TYPE, ...). The rules governing the setting of x to the value of y at the beginning of the call are exactly the same as those of an assignment x := y: not just the type rule, as expressed by type conformance (the type of y must conform to SOME_TYPE), but also the actual runtime effect which, as for assignments, is either a reference attachment or, for expanded types, a copy.
Note that the ability to accept the assignment x := Void for x of any reference type (section 9.6.10) is a consequence of the type conformance rule because Void is of type NONE, which by construction (section 9.5.4) conforms to all types.
Previous | Table of Contents | Next |