Previous Table of Contents Next


9.9.2. Redefinition

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 version—such as the ACCOUNT implementation of deposit in this example—is called the precursor of the new version. It is common for a redefinition to rely on the precursor’s 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 ACCOUNT’s 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.

9.9.3. Polymorphism

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