Previous | Table of Contents | Next |
Dynamic binding is particularly interesting for polymorphic data structures. If we iterate over the list of accounts of various kinds, accounts: LIST [ACCOUNT], illustrated in Figure 9.10, and at each step let acc represent the current list element, we can repeatedly apply acc.deposit (...) to have the appropriate variant of the deposit operation triggered for each element.
The benefit of such techniques appears clearly if we compare them with the traditional way to address such needs: using multibranch discriminating instructions of the form if Account is a savings account then ...elseif It is a money market account then ... and so on, or the corresponding case ... of ...or inspect instructions. Apart from their heaviness and complexity, such solutions cause many components of a software system to rely on the knowledge of the exact set of variants available for a certain notion, such as bank account. Then, any addition, change, or removal of variants can cause a ripple of changes throughout the architecture. This is one of the major obstacles to extendibility and reusability in traditional approaches. In contrast, using the combination of inheritance, redefinition, polymorphism, and dynamic binding makes it possible to have a point of single choicea unique location in the system that knows the exhaustive list of variants. Every client then manipulates entities of the most general type, ACCOUNT, through dynamically bound calls of the form acc.some_account_feature (...).
These observations make dynamic binding appear for what it is: not an implementation mechanism, but an architectural technique that plays a key role (along with information hiding, which it extends, and design by contract, to which it is linked through the assertion redefinition rules seen later) in providing the modular system architectures of Eiffel, the basis for the methods approach to reusability and extendibility. These properties apply as early as analysis and modeling, and continue to be useful throughout the subsequent steps.
In the preceding examples of dynamic binding, all classes were assumed to be fully implemented, and dynamically bound features had a version in every relevant class, including the most general ones such as ACCOUNT.
It is also useful to define classes that leave the implementation of some of their features entirely to proper descendants. Such an abstract class is known as deferred; so are its unimplemented features. The reverse of deferred is effective, meaning fully implemented.
LIST is a typical example of deferred class. As it describes the general notion of list, it should not favor any particular implementation; that is, the task of its effective descendants, such as LINKED_LIST (linked implementation), TWO_WAY_LIST (linked both ways), and ARRAYED_LIST (implementation by an array), all effective, and all indeed to be found in ISEs EiffelBase libraries.
At the level of the deferred class LIST, some features such as extend (add an item at the end of the list) have no implementation and hence are declared as deferred. Here is the corresponding form, illustrating the syntax for both deferred classes and their deferred features:
indexing description: Sequential finite lists, without a commitment to a representation deferred class LIST [G] feature -- Access count: INTEGER is -- Number of items in list do ... See below; this feature can be effective ... end feature -- Element change extend (x: G) is -- Add x at end of list. require space_available: not full deferred ensure one_more: count = old count + 1 end ... Other feature declarations and invariant ... end -- class LIST
A deferred feature (considered to be a routine, although it can yield an attribute in a proper descendant) has the single keyword deferred in lieu of the do Instructions clause of an effective routine. A deferred classdefined as a class that has at least one deferred featuremust be introduced by deferred class instead of just class.
As the example of extend shows, a deferred feature, although it has no implementation, can be equipped with assertions. They are binding on implementations in descendants, in a way to be explained later.
Deferred classes do not have to be fully deferred. They can contain some effective features along with their deferred ones. Here, for example, we may express count as a function:
count: INTEGER is -- Number of items in list do from start until after loop Result := Result + 1; forth end end
Previous | Table of Contents | Next |