Author:halw

Date:2008-10-14T17:15:25.000000Z


git-svn-id: https://svn.eiffel.com/eiffel-org/trunk@81 abb3cda0-5349-4a8f-a601-0c33ac3a8c38
This commit is contained in:
halw
2008-10-14 17:15:25 +00:00
parent 062493b5b9
commit 7bf0b00d90
6 changed files with 110 additions and 79 deletions

View File

@@ -9,11 +9,19 @@ Clearly, not everyone using Eiffel will follow to the letter the principles outl
==Clusters and the cluster model==
Unlike earlier approaches, the Eiffel model assumes that the system is divided into a number of subsystems or '''clusters'''. It keeps from the Waterfall a sequential approach to the development of each cluster (without the gaps), but promotes '''concurrent engineering''' for the overall process, as suggested by the following picture:
[[Image:tutorial-2]]
[[Image:tutorial-2]]
The Eiffel techniques developed below, in particular information hiding and Design by Contract, make the concurrent engineering process possible by letting the clusters rely on each other through clearly defined interfaces, strictly limiting the amount of knowledge that one must acquire to use the cluster, and permitting separate testing. When the inevitable surprises of a project happen, the project leader can take advantage of the model's flexibility, advancing or delaying various clusters and steps through dynamic reallocation of resources.
Each of the individual cluster life cycles is based on a continuous progression of activities, from the more abstract to the more implementation-oriented:
[[Image:tutorial-3]]
[[Image:tutorial-3]]
You may view this picture as describing a process of accretion (as with a stalactite), where each steps enriches the results of the previous one. Unlike traditional views, which emphasize the multiplicity of software products -- analysis document, global and detailed design documents, program, maintenance reports... --, the principle is here to treat the software as a '''single product''' which will be repeatedly refined, extended and improved. The Eiffel language supports this view by providing high-level notations that can be used throughout the lifecycle, from the most general and software-independent activities of system modeling to the most exacting details of implementation tuned for optimal run-time performance.
These properties make Eiffel span the scope of both "object-oriented methods", with their associated notations such as UML and supporting CASE tools (whereas most such solutions do not yield an executable result), and "programming languages" (whereas most such languages are not suitable for design and analysis).

View File

@@ -4,27 +4,31 @@
[[Property:uuid|912e4c38-9add-e478-59c3-5c10aa75d784]]
Genericity and inheritance, the two fundamental mechanisms for generalizing classes, may be combined in two fruitful ways.
The first technique yields '''polymorphic data structures'''. Assume that in the generic class <code> LIST [ G ] </code> the insertion procedure <code> put </code> has a formal argument of type <code> G </code>, representing the element to be inserted. Then with a declaration such as
<code>pl: LIST [POLYGON]</code>
The first technique yields '''polymorphic data structures'''. Assume that in the generic class <code> LIST [G] </code> the insertion procedure <code> put </code> has a formal argument of type <code> G </code>, representing the element to be inserted. Then with a declaration such as
<code>
pl: LIST [POLYGON]</code>
the type rules imply that in a call <code> pl </code>. <code> put </code> <code> ( "p" ) </code> the permitted types for the argument <code> p </code> include not just <code> POLYGON </code>, but also <code> RECTANGLE </code> (an heir of <code> POLYGON </code>) or any other type conforming to <code> POLYGON </code> through inheritance.
the type rules imply that in a call<code> pl.put ( "p" ) </code>the permitted types for the argument <code> p </code> include not just <code> POLYGON </code>, but also <code> RECTANGLE </code> (an heir of <code> POLYGON </code>) or any other type conforming to <code> POLYGON </code> through inheritance.
The basic conformance requirement used here is the inheritance-based type compatibility rule: <code> V </code> conforms to <code> T </code> if <code> V </code> is a descendant of <code> T </code>.
Structures such as <code> pl </code> may contain objects of different types, hence the name "polymorphic data structure". Such polymorphism is, again, made safe by the type rules: by choosing an actual generic parameter ( <code> POLYGON </code> in the example) based higher or lower in the inheritance graph, you extend or restrict the permissible types of objects in <code> pl </code>. A fully general list would be declared as
<code>LIST [ANY]</code>
<code>
LIST [ANY]</code>
where <code> ANY </code>, a Kernel Library class, is automatically an ancestor of any class that you may write.
The other mechanism for combining genericity and inheritance is '''constrained genericity'''. By indicating a class name after a formal generic parameter, as in
<code>VECTOR [T -> NUMERIC]</code>
<code>
VECTOR [T -> NUMERIC]</code>
you express that only descendants of that class (here <code> NUMERIC </code>) may be used as the corresponding actual generic parameters. This makes it possible to use the corresponding operations. Here, for example, class <code> VECTOR </code> may define a routine <code> infix </code> "+" for adding vectors, based on the corresponding routine from <code> NUMERIC </code> for adding vector elements. Then by making <code> VECTOR </code> itself inherit from <code> NUMERIC </code>, you ensure that it satisfies its own generic constraint and enable the definition of types such as <code> VECTOR </code> <code> [ </code> <code> VECTOR </code> <code> [ </code> <code> T </code> <code> ]] </code>.
you express that only descendants of that class (here <code> NUMERIC </code>) may be used as the corresponding actual generic parameters. This makes it possible to use the corresponding operations. Here, for example, class <code> VECTOR </code> may define a routine <code> infix </code> "+" for adding vectors, based on the corresponding routine from <code> NUMERIC </code> for adding vector elements. Then by making <code> VECTOR </code> itself inherit from <code> NUMERIC </code>, you ensure that it satisfies its own generic constraint and enable the definition of types such as<code> VECTOR [VECTOR [T]]</code> .
As you have perhaps guessed, unconstrained genericity, as in <code> LIST </code> <code> [ G ] </code>, may be viewed as an abbreviation for genericity constrained by <code> ANY </code>, as in
<code>LIST [G -> ANY]</code>
As you have perhaps guessed, unconstrained genericity, as in<code> LIST [G]</code> , may be viewed as an abbreviation for genericity constrained by <code> ANY </code>, as in
<code>
LIST [G -> ANY]</code>
Something else you may have guessed: if <code> ANY </code>, introduced in this session, is the top of the inheritance structure -- providing all classes with universal features such as <code> equal </code> to compare arbitrary objects and <code> clone </code> to duplicate objects -- then <code> NONE </code>, seen earlier in the notation <code> feature </code> <code> { </code> <code> NONE </code> <code> } </code>, is its bottom. <code> NONE </code> indeed conceptually inherits from all other classes. <code> NONE </code> is, among other things, the type of <code> ensure </code>, the void reference.
Something else you may have guessed: if <code> ANY </code>, introduced in this session, is the top of the inheritance structure -- providing all classes with universal features such as <code> equal </code> to compare arbitrary objects and <code> clone </code> to duplicate objects -- then <code> NONE </code>, seen earlier in the notation<code> feature {NONE} </code>, is its bottom. <code> NONE </code> indeed conceptually inherits from all other classes. <code> NONE </code> is, among other things, the type of <code> ensure </code>, the void reference.

View File

@@ -13,13 +13,13 @@ deferred class
feature
dues_paid (year: INTEGER): BOOLEAN is
dues_paid (year: INTEGER): BOOLEAN
do ... end
valid_plate (year: INTEGER): BOOLEAN is
valid_plate (year: INTEGER): BOOLEAN
do ... end
register (year: INTEGER) is
register (year: INTEGER)
-- Register vehicle for year.
require
dues_paid (year)
@@ -34,17 +34,26 @@ end -- VEHICLE
</code>
This example assumes that no single registration algorithm applies to all kinds of vehicle; passenger cars, motorcycles, trucks etc. are all registered differently. But the same precondition and postcondition apply in all cases. The solution is to treat register as a deferred routine, making VEHICLE a deferred class. Descendants of class VEHICLE, such as CAR or TRUCK, effect this routine, that is to say, give effective versions. An effecting is similar to a redefinition; only here there is no effective definition in the original class, just a specification in the form of a deferred routine. The term '''redeclaration''' covers both redefinition and effecting.
[[Image:invitation-5]]
[[Image:invitation-5]]
Whereas an effective class described an implementation of an abstract data types, a deferred class describes a set of possible implementations. You may not instantiate a deferred class: create v is invalid if v is declared of type VEHICLE. But you may assign to v a reference to an instance of an effective descendant of VEHICLE. For example, assuming CAR and TRUCK provide effective definitions for all deferred routines of VEHICLE, the following will be valid:
<code>
v: VEHICLE; c: CAR; t: TRUCK
... create c; create t ...
if "Some test" then
v: VEHICLE
c: CAR
t: TRUCK
...
create c
create t
...
if "Some test" then
v := c
else
else
v := t
end
v.register (2003)</code>
end
v.register (2008)</code>
This example fully exploits polymorphism: depending on the outcome of "Some test", <code>v</code> will be treated as a car or a truck, and the appropriate registration algorithm will be applied. Also, "Some test" may depend on some event whose outcome is impossible to predict until run-time, for example the user clicking with the mouse to select one among several vehicle icons displayed on the screen.

View File

@@ -11,7 +11,7 @@ In some circumstances it is useful to define an object that denotes an operation
each involves a '''control''' (here the OK button), an '''event''' (mouse click) and an '''operation''' (update the file). This can be programmed by having an "event loop", triggered for each event, which performs massive decision-making ( <code>if "The latest event was `left mouse click on button 23'" then "Appropriate instructions" else if </code>... and so on with many branches); but this leads to bulky software architectures where introducing any new control or event requires updating a central part of the code. It's preferable to let any element of the system that encounters a new control-event-operation association
<code>
[control, event, operation]
[control, event, operation]
</code>
store it as a triple of objects into an object structure, such as an array or a list. Triples in that structure may come from different parts of the system; there is no central know-it-all structure. The only central element is a simple mechanism which can explore the object structure to execute each <code> operation </code> associated with a certain <code> control </code> and a certain <code> event </code>. The mechanism is not just simple; it's also independent of your application, since it doesn't need to know about any particular control, event or operation (it will find them in the object structure). So it can be programmed once and for all, as part of a library such as EiffelVision 2 for platform-independent graphics.

View File

@@ -22,7 +22,11 @@ end -- ARRAYED_LIST
The <code> inherit </code> ... clause lists all the "parents" of the new class, which is said to be their "heir". (The "ancestors" of a class include the class itself, its parents, grandparents etc.; the reverse term is "descendant".) Declaring <code> ARRAYED_LIST </code> as shown ensures that all the features and properties of lists and arrays are applicable to arrayed lists as well. Since the class has more than one parent, this is a case of multiple inheritance.
Standard graphical conventions -- drawn from the Business Object Notation or BON, a graphical object-oriented notation based on concepts close to those of Eiffel, and directly supported by EiffelStudio -- illustrate such inheritance structures:
[[Image:invitation-4]]
[[Image:invitation-4]]
An heir class such as <code> ARRAYED_LIST </code> needs the ability to define its own export policy. By default, inherited features keep their export status (publicly available, secret, available to selected classes only); but this may be changed in the heir. Here, for example, <code> ARRAYED_LIST </code> will export only the exported features of <code> LIST </code>, making those of <code> ARRAY </code> unavailable directly to <code> ARRAYED_LIST </code> 's clients. The syntax to achieve this is straightforward:
<code>
class ARRAYED_LIST [G]

View File

@@ -4,7 +4,7 @@
[[Property:uuid|1c3221be-0237-1c9a-407d-652a4084de12]]
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 remarkable facility must be reconciled with static typing. The language convention is simple: an assignment of the form <code> a : = 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.)
@@ -24,7 +24,7 @@ feature -- Specific features of rectangles, such as:
side1: REAL
side2: REAL
perimeter: REAL is
perimeter: REAL
-- Rectangle-specific version
do
Result := 2 * (side1 + side2)
@@ -36,14 +36,20 @@ feature -- Specific features of rectangles, such as:
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>
<code>
p: POLYGON
r: RECTANGLE
...
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'''.
create p
create r
...
if c then
p := r
end
print (p.perimeter)</code>
The polymorphic assignment<code> p := 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.
@@ -51,13 +57,13 @@ This technique is particularly attractive when compared to its closest equivalen
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.
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.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.
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.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&#153; 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.