Eiffel Classes
The unit of software reuse in Eiffel is the class.
The unit of modularity in Eiffel is the class.
The unit of type modeling in Eiffel is the class.
All Eiffel code must exist within the context of a class.
In Eiffel, application systems, or simply systems, are created by assembling a set of related classes. The classes in a system will be related only by one or both of the two allowable relationships in object-oriented design.
Having read the above, you should be convinced that the concept of class is important and far-reaching. The fact that we have precise rules about classes simplifies life a lot. The only kind of module in Eiffel is a class. Each class exists in one source file (which contains only that class), and contains the code necessary to provide a static definition of a data type. Every runtime entity, i.e. every object, must be an instance of a class. Because we can depend upon these things in Eiffel, we have consistency and predictabililty in the inherently complex world of software development.
Let's take a look at how classes are structured.
The code that makes up an Eiffel class is divided into the following parts:
Structure of a Class
All of the above, except Class header, are optional. So the simplest Eiffel class you could build would look like this:
class
SIMPLE
end
Okay, so class SIMPLE is only interesting in its simplicity. Let's look at an example that is more illustrative:
note
description: Objects that model lists
revision: $Revision: 1.4 $
class
OLD_FASHIONED_LIST [G]
obsolete "This class is obsolete, use LINKED_LIST [G] instead"
inherit
DYNAMIC_LIST [G]
create
make
feature -- Initialization
make
-- Create an empty list.
do
before := True
ensure
is_before: before
end
feature -- Access
item: G
-- Current item
do
Result := active.item
end
first: like item
-- Item at first position
do
Result := first_element.item
end
... other features omitted ...
invariant
before_constraint: before implies (active = first_element)
after_constraint: after implies (active = last_element)
Here is a class that, although completely contrived, utilizes all of the required and optional parts of the class. Let's look at each part individually.
Note
note
description: Objects that model lists
revision: $Revision: 1.4 $
The note
part of a class is there to allow you as a producer to record information of your choice which will help you or other reuse consumers at some later time to locate understand the class. This important in Eiffel because we try to treat every class as if someday it will become reusable.
Information in note
does not change the semantics of the class.
The note
part in the class above is typical. It is introduced with the language keyword note
, and contains two note clauses, each of which is comprised of an index and a single index value. You can code note clauses with indexes that you devise yourself, so there is nothing inherently special about "description
" and "revision
" as used above. But, these indexes could be special to tools which analyze libraries of classes use them. Although these clauses have only one index value each, it is permissible to put more, separated by commas.
Class Header
class
OLD_FASHIONED_LIST [G]
The class header is introduced by the keyword "class", which in turn can be preceded by one of three keywords which mark the class as deferred
, expanded
, or frozen
. In our example, the class has none of these markings, so it is an effective class whose instances are access by reference.
The keyword class is followed by the class name, in this case "OLD_FASHIONED_LIST
".
Of the three keywords for header marks, the one which you will encounter most often is deferred
. A class is deferred if it contains one or more features that are deferred, that is, features which have been specified in the class but for which no implementation has been provided. Proper descendants of a deferred class will provide implementations for its deferred features.
Formal Generics
class
OLD_FASHIONED_LIST [G]
In this example the class name is followed by the specification of one formal generic parameter "G
". The presence of one or more formal generic parameters will designate a class as a generic class. The formal generic parameter is a place holder for a class name which will be provided by reuse consumers. For example if we wrote a class which was a client to OLD_FASHIONED_LIST
we would substitute the class name for the type of objects that we would want to build an OLD_FASHIONED_LIST
of. We might make this declaration:
my_list_of_cats: OLD_FASHION_LIST [CAT]
The entity my_list_of_cats
could then be attached at runtime to an OLD_FASHIONED_LIST
of objects of type CAT
. So the class CAT
becomes an actual generic parameter and substitutes for G
in the declaration.
Of course formal generic parameters cannot be the same name as a class name in the same universe. If multiple formal generic parameters are used, they are separated by commas.
You will learn more about generic classes in the section titled Genericity .
Obsolete
obsolete "This class is obsolete, use LINKED_LIST [G] instead"
OLD_FASHION_LIST
s are obsolete ... and the class is marked as such by include the line above. The manifest string contains an explanation, instructions, and/or recommended alternatives. Compilers and other language tools can deliver this message to potential reuse consumers. As with note
, obsolete
has no effect on the semantics of the class.
Obsolete is rarely used because of the nature of certain elements of the Eiffel methodology. For example, if implementations are well-hidden behind implementation-independent specifications, then those implementations may be changed to adapt the class to changing execution environments in such a way that clients are unaffected.
Inheritance
inherit
DYNAMIC_LIST [G]
One of the two possible relationships between classes, inheritance is also a powerful software reuse mechanism. In this example class OLD_FASHIONED_LIST
declares itself to be a proper descendant of class DYNAMIC_LIST
.
There will be more in the section called . For now though, be aware of two important implications of this declaration:
- Every feature of
DYNAMIC_LIST
is available toOLD_FASHIONED_LIST
and potentially available to its clients. - Whenever an instance of
DYNAMIC_LIST
is called for, then an instance ofOLD_FASHIONED_LIST
will suffice.
Creators
create
make
The creators part of a class declares a procedure as being a creation procedure. In this case the procedure in question is the one named make
. By convention, creation procedure names begin with the word " make
".
Let's take a quick look at object creation. Consider this declaration: my_list_of_cats: OLD_FASHION_LIST [CAT]
Here the entity my_list_of_cats
can be attached to an object of type OLD_FASHION_LIST [CAT]
at runtime. The process of converting my_list_of_cats
from holding a void reference to holding a reference to a object modeling a list of cats, starts when a creation instruction is executed. The creation instruction creates the instance and may apply a creation procedure to initialize the instance. A creation instruction for the declaration above would look like this: create my_list_of_cats.make
The create
keyword is used to introduce a creation instruction. This instruction causes the following four things to happen:
- A shell of a new instance of
OLD_FASHION_LIST [CAT]
is created in memory with a memory field for every attribute - Each field is initialized with standard default values
- False for type
BOOLEAN
- Null character for type
CHARACTER
- The appropriate form of zero for number types
-
Void
for reference types
- False for type
- Attach the new instance to the entity
my_list_of_cats
- Apply the creation procedure
make
Once these steps complete successfully, my_list_of_cats
will be attached to a valid instance (i.e., an instance in which the class invariant is true) of OLD_FASHIONED_LIST [CAT]
.
Features
feature -- Initialization
make
-- Create an empty list.
do
before := True
ensure
is_before: before
end
feature -- Access
item: G
-- Current item
do
Result := active.item
end
first: like item
-- Item at first position
do
Result := first_element.item
end
The features part of a class is the area in which we feel that most of the "programming" is done. It is here that we define those things that instances of a class have and can do. We will learn more about features in the next section Adding Class Features .
Until then let's just take a quick look at how features fit into a class. Notice that in our example the features part is introduced by the keyword "feature
". In fact there are two occurrences of feature
in this example, each followed by a comment.
You may declare multiple feature
statements. This helps you group features in a manner that makes sense. Here we see the first group contains those features which are listed as creation procedures in the creators part of the class. The second group of features labeled "Access
" contains a set of queries available to clients of the class.
Although the words "Initialization
" and "Access
" are actually in comments after the feature
keyword, some language processing tools apply some significance to these, for example, ordering the groups in "pretty-printed" views of a class. Also, some tools allow you to build templates for creating new classes which have feature
clauses already in place for predetermined groups.
Invariant
invariant
before_constraint: before implies (active = first_element)
after_constraint: after implies (active = last_element)
Here's the last word in a class definition ... both literally and figuratively. The invariant part, introduced not surprisingly by the keyword "invariant
", is that portion of the class in which we can state what it means for an object to be a valid instance of this class.
We will learn more about class invariants in the section titled Design by Contract and Assertions .