Previous Table of Contents Next


In many cases, the body is the same as in the precursor. Then explicit redefinition is unbearably tedious, implying constant text duplication. The mechanism of anchored redeclaration solves this problem. The original declaration of set_owner in ACCOUNT should be of the form

   set_owner (h: like Current) is
            -- Make h the account owner.
            -- The rest as before:
       require
           not_void: h /= Void
       do
           owner := h
       end

A like anchor type, known as an anchored type, may appear in any context in which anchor has a well-defined type T: anchor can be an attribute or function of the enclosing class or an argument of the enclosing routine. Then, in the class in which it appears, type like anchor means the same as T; for example, in set_owner, the declaration of h has the same effect as if h had been declared of type HOLDER, the type of the anchor owner in class ACCOUNT. The difference comes in proper descendants: If a type redefinition changes the type of the anchor, any entity declared like the anchor is considered to have been redefined too. This is a form of implicit type redeclaration.

In the example, class BUSINESS_ACCOUNT need only redefine the type of owner (to BUSINESS). Then there is no need to redefine set_owner—unless we want to change its implementation or assertions.

It is possible to use Current as anchor; the declaration like Current denotes a type based on the current class (with the same generic parameters, if any). This is in fact a common case; we saw in section 9.9.7 that it applies in class COMPARABLE to features such as

   infix “<” (other: like Current): BOOLEAN is ...

because we only want to compare two comparable elements of compatible types—but not, for example, integer and strings, even if both types conform to COMPARABLE. (A “balancing rule” makes it possible, however, to mix the various arithmetic types, consistently with mathematical traditions, in arithmetic expressions such as 3 + 45.82 or boolean expressions such as 3 < 45.82.)

Similarly, procedure copy is declared in class GENERAL as

   copy (other: like Current) is ...

with both the argument anchored to the current object. Function clone, for its part, has signature clone (other: ANY): like other, showing a result anchored to the argument so that the type of clone (x) for any x is the same as the type of x.

A final, more application-oriented example of anchoring to Current is the feature merge posited in an earlier example (section 9.6.14) with the signature merge (other: ACCOUNT). By using instead merge (other: like Current), we can ensure that in any descendant class—BUSINESS_ACCOUNT, SAVINGS_ACCOUNT, MINOR_ACCOUNT, and so on—an account is only mergeable with another of a compatible type.

Covariance complicates somewhat the static type checking mechanism; mechanisms of system validity and catcalls address the problem, which is discussed in detail in the book Object-Oriented Software Construction (Meyer, 1997).

9.10. Other Important Mechanisms

We now examine a few supplementary mechanisms that complement the preceding picture: shared objects, constants, instructions, and lexical conventions.

9.10.1. once Routines and Shared Objects

The Eiffel’s method obsession with extendibility, reusability, and maintainability yields, as has been seen, highly modular and decentralized architectures, where intermodule coupling is limited to the strictly necessary, interfaces are clearly delimited, and all the temptations to introduce obscure dependencies, in particular global variables, have been removed. There is a need, however, to let various components of a system have access to common objects without requiring their routines to pass these objects around as arguments (which is only slightly better than global variables). For example, various classes may need to perform output to a common console window, represented by a shared object.

Eiffel addresses this need through an original mechanism that also takes care of another important issue, poorly addressed by many design and programming approaches: initialization. The basic idea is very simple: If instead of do, the implementation of an effective routine is introduced by the keyword once, it is only executed the first time the routine is called during a system execution (or, in a multithreaded environment, the first time in each thread), regardless of what the caller is. Subsequent calls from the same caller or others have no effect; if the routine is a function, it always returns the result computed by the first call—object if an expanded type, reference otherwise.


Previous Table of Contents Next