mirror of
https://github.com/EiffelSoftware/eiffel-org.git
synced 2025-12-07 23:32:42 +01:00
Date:2008-09-17T13:53:28.000000Z git-svn-id: https://svn.eiffel.com/eiffel-org/trunk@3 abb3cda0-5349-4a8f-a601-0c33ac3a8c38
66 lines
7.3 KiB
Plaintext
66 lines
7.3 KiB
Plaintext
[[Property:title|11 Polymorphism and Dynamic Binding]]
|
|
[[Property:link_title|I2E: Polymorphism and Dynamic Binding]]
|
|
[[Property:weight|-4]]
|
|
Inheritance is not just a module combination and enrichment mechanism. It also enables the definition of flexible entities that may become attached to objects of various forms at run time, a property known as polymorphism.
|
|
|
|
This remarkable facility must be reconciled with static typing. The language convention is simple: an assignment of the form <code> a </code> <code> : </code> <code> b </code> is permitted not only if <code> a </code> and <code> b </code> are of the same type, but more generally if <code> a </code> and <code> b </code> are of reference types <code> A </code> and <code> B </code>, based on classes <code> A </code> and <code> B </code> such that <code> B </code> is a descendant of <code> A </code>.
|
|
|
|
This corresponds to the intuitive idea that a value of a more specialized type may be assigned to an entity of a less specialized type -- but not the reverse. (As an analogy, consider that if you request vegetables, getting green vegetables is fine, but if you ask for green vegetables, receiving a dish labeled just "vegetables" is not acceptable, as it could include, say, carrots.)
|
|
|
|
What makes this possibility particularly powerful is the complementary facility: '''feature redefinition'''. A class may redefine some or all of the features which it inherits from its parents. For an attribute or function, the redefinition may affect the type, replacing the original by a descendant; for a routine it may also affect the implementation, replacing the original routine body by a new one.
|
|
|
|
Assume for example a class <code> POLYGON </code>, describing polygons, whose features include an array of points representing the vertices and a function <code> perimeter </code> which computes a polygon's perimeter by summing the successive distances between adjacent vertices. An heir of <code> POLYGON </code> may begin as:
|
|
<code>
|
|
class RECTANGLE
|
|
inherit
|
|
POLYGON
|
|
redefine
|
|
perimeter
|
|
end
|
|
|
|
feature -- Specific features of rectangles, such as:
|
|
|
|
side1: REAL
|
|
side2: REAL
|
|
|
|
perimeter: REAL is
|
|
-- Rectangle-specific version
|
|
do
|
|
Result := 2 * (side1 + side2)
|
|
end
|
|
|
|
... Other RECTANGLE features ...
|
|
</code>
|
|
|
|
Here it is appropriate to redefine <code> perimeter </code> for rectangles as there is a simpler and more efficient algorithm. Note the explicit <code> redefine </code> sub clause (which would come after the <code> rename </code> if present).
|
|
|
|
Other descendants of <code> POLYGON </code> may also have their own redefinitions of <code> perimeter </code>. The version to use in any call is determined by the run-time form of the target. Consider the following class fragment:
|
|
<code>p: POLYGON; r: RECTANGLE
|
|
... create p; create r ...
|
|
if c then
|
|
p := r
|
|
end
|
|
print (p.perimeter)</code>
|
|
|
|
The polymorphic assignment <code> p </code> := <code> r </code> is valid because of the above rule. If condition <code> c </code> is false, <code> p </code> will be attached to an object of type <code> POLYGON </code> for the computation of <code> p </code>. <code> perimeter </code>, which will thus use the polygon algorithm. In the opposite case, however, <code> p </code> will be attached to a rectangle; then the computation will use the version redefined for <code> RECTANGLE </code>. This is known as '''dynamic binding'''.
|
|
|
|
Dynamic binding provides a high degree of flexibility. The advantage for clients is the ability to request an operation (such as perimeter computation) without explicitly selecting one of its variants; the choice only occurs at run-time. This is essential in large systems, where many variants may be available; dynamic binding protects each component against changes in other components.
|
|
|
|
This technique is particularly attractive when compared to its closest equivalent in traditional approaches, where you would need records with variant components, or union types (C), together with <code> case </code> (switch) instructions to discriminate between variants. This means that every client must know about every possible case, and that any extension may invalidate a large body of existing software.
|
|
|
|
The combination of inheritance, feature redefinition, polymorphism and dynamic binding supports a development mode in which every module is open and incremental. When you want to reuse an existing class but need to adapt it to a new context, you can define a new descendant of that class (with new features, redefined ones, or both) without any change to the original. This facility is of great importance in software development, an activity that -- by design or circumstance -- is invariably incremental.
|
|
|
|
The power of these techniques demands adequate controls. First, feature redefinition, as seen above, is explicit. Second, because the language is typed, a compiler can check statically whether a feature application <code> a </code>. <code> f </code> is correct. In contrast, dynamically typed object-oriented languages defer checks until run-time and hope for the best: if an object "sends a message" to another (that is to say, calls one of its routines) one just expects that the corresponding class, or one of its ancestors, will happen to include an appropriate routine; if not, a run-time error will occur. Such errors will not happen during the execution of a type-checked Eiffel system.
|
|
|
|
In other words, the language reconciles dynamic binding with static typing. Dynamic binding guarantees that whenever more than one version of a routine is applicable the right version (the one most directly adapted to the target object) will be selected. Static typing means that the compiler makes sure there is at least one such version.
|
|
|
|
This policy also yields an important performance benefit: in contrast with the costly run-time searches that may be needed with dynamic typing (since a requested routine may not be defined in the class of the target object but inherited from a possibly remote ancestor), the EiffelStudio implementation always finds the appropriate routine in constant-bounded time.
|
|
|
|
Assertions provide a further mechanism for controlling the power of redefinition. In the absence of specific precautions, redefinition may be dangerous: how can a client be sure that evaluation of <code> p </code>. <code> perimeter </code> will not in some cases return, say, the area? Preconditions and postconditions provide the answer by limiting the amount of freedom granted to eventual redefiners. The rule is that any redefined version must satisfy a weaker or equal precondition and ensure a stronger or equal postcondition than in the original. This means that it must stay within the semantic boundaries set by the original assertions.
|
|
|
|
The rules on redefinition and assertions are part of the Design by Contract™ theory, where redefinition and dynamic binding introduce subcontracting. <code> POLYGON </code>, for example, subcontracts the implementation of perimeter to <code> RECTANGLE </code> when applied to any entity that is attached at run-time to a rectangle object. An honest subcontractor is bound to honor the contract accepted by the prime contractor. This means that it may not impose stronger requirements on the clients, but may accept more general requests: weaker precondition; and that it must achieve at least as much as promised by the prime contractor, but may achieve more: stronger postcondition.
|
|
|
|
|
|
|
|
|