[[Property:title|Adding Class Features]]
[[Property:weight|2]]
[[Property:uuid|04261c22-b28f-b045-a5d7-2c85efe992b9]]
The features of a class make it useful. They are the things that objects which are instances of the class have and can do.
It is during the process of adding class features that we relate a class we are producing to other classes via the client/supplier relationship.
It is when we add features to a class that we can build the executable code that makes things happen.
==Categorizing Features==
In Eiffel, we have several ways of thinking abstractly about features and categorizing them. As you saw in [[Eiffel Classes|Eiffel Classes]] the feature
clause gives us a way to group features in the software text. We have ways to group features more generally, too. Here are some.
===Source===
You remember the example class from [[Eiffel Classes|Eiffel Classes]] :
note
description: Objects that model lists
revision: $Revision: 1.5 $
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)
end
The example shows three of the features ( make
, item
, and first
) coded directly in the class. These features (and the ones omitted from the example in the interest of brevity) may not be the only features of the class OLD_FASHIONED_LIST
. In fact we can just about guarantee that there are other features of this class. Remember the inheritance part of the class:
inherit
DYNAMIC_LIST [G]
This means that every feature in DYNAMIC_LIST
will be a feature of OLD_FASHIONED_LIST
. So one way we can think of features is by their source.
* Immediate features are those that are introduced by a class itself.
* Inherited features are those that come to the class from its proper ancestors.
We will see more about this in [[Inheritance|Inheritance]] .
===Implementation Type===
Whenever we are building a class in Eiffel, we are potential reuse producers. As such, we can categorize the features of a class based on the three types of feature implementation:
* Attribute
* Function
* Procedure
Attributes are those features which occupy storage in an instance. When you code an attribute in a class that you are producing, that class becomes a client to the class of the attribute. This is the most common way to establish a client/supplier relationship between the classses. Client/supplier is one of the two relationships that can exist between classes.
Functions and procedures are the computation features, that is they are features that involve executable code. Functions and procedures together are themselves categorized as routines.
Also, from the producer standpoint we may view features as whether they work from memory or through computation:
* Memory
** Attribute
* Computation
** Function
** Procedure
This view can be valuable when a producer is considering performance issues. For example, if there is some class function called count
, a producer might investigate whether it is better to leave count
as a function, computing it each time the feature is applied, or to change it to an attribute and compute it less often.
===Usage===
A times we find it appropriate to categorize features by how we use them. Specifically, as:
* Query
** Attribute
** Function
* Command
** Procedure
Queries are features that, when applied to an instance, provide a value in response. Commands instruct an intance to take some action, but do not return a value. Seeing features as either queries or commands is of primary interest to reuse consumers. But as producers there are important reasons for ensuring that when we implement a feature, we implement it as a query or as a command, but not both. We will see more about this in [[Design by Contract and Assertions|Design by Contract and Assertions]] .
==General Syntax of Features==
===More Sample Features===
Here is another example class. Again this class contrived, so it does not do anyhing worthwhile except show you what different types of features look like. This class has an attribute and a constant attribute, and functions and procedures both with and without arguments.
class
SOME_CLASS
create
make,
make_with_arguments
feature -- Initialization
make
-- Creation procedure
do
an_attribute := 5
end
make_with_arguments (hour: INTEGER; minute: INTEGER; second: INTEGER)
-- Another creation procedure
do
an_attribute := second + (minute * 60) + (hour * 3600)
end
feature -- Access
an_attribute: INTEGER
-- An attribute of type INTEGER
another_attribute: INTEGER = 46
-- A constant attribute
a_function: STRING is
-- A function without arguments
do
Result := an_attribute.out
end
another_function (an_int: INTEGER): INTEGER
-- A function with arguments
do
Result := an_attribute + an_int
end
feature -- Basic Operations
a_procedure
-- A procedure with no arguments
do
an_attribute := an_attribute + 5
end
another_procedure (an_int: INTEGER; another_int: INTEGER)
-- A procedure with arguments
do
an_attribute := an_attribute + an_int + another_int
end
end -- Class SOME_CLASS
===Feature Declaration===
When you write a feature in a class, you typically will include some of the following:
* The feature's name
{{note|In Eiffel every feature of a class must have a name that is unique within that class. }}
* The feature's type (in the case of an attribute or a function)
* The feature's formal argument list (in the case of a function or procedure that has arguments)
* The actual value of the feature (in the case of a constant attribute)
* The implementation code (in the case of a function or procedure)
Let's dissect one feature and identify its parts:
another_function (an_int: INTEGER): INTEGER
-- A function with arguments
do
Result := an_attribute + an_int
end
In this feature:
* "another_function" is the feature's name.
* "(an_int: INTEGER)" is the argument list.
** "an_int" is the name of the first argument
** "INTEGER" is the type "an_int"
* "INTEGER" (after the argument list) is the feature's type.
* "do " introduces the implementation code.
* "Result := an_attribute + an_int" is an instruction.
{{note|This feature is a function. As a consequence the computation uses the keyword "Result' as an entity name for the value to be returned by the function }}
* "end" ends the feature declaration
==General Structure of Routines==
As you can imagine, the possibilities for coding routines are more complex than those for coding attributes. Routines always contain the keyword "is" after the first part of the feature declaration. The part after the "is" is called the routine part.
The routine part is made up of the following sections:
* [[#Header Comment|Header Comment]]
* [[#obsolete|Obsolete]]
* [[#Precondition|Precondition]]
* [[#Local Declarations|Local Declarations]]
* [[#Routine Body|Routine Body]]
* [[#Postcondition|Postcondition]]
* [[#Rescue|Rescue]]
All of the sections except the Routine Body are optional.
Here is another feature, a routine, which has all of these except obsolete and rescue.
insert_text_header_item (a_label: STRING;
a_width, a_format: INTEGER; insert_after_item_no: INTEGER)
-- Insert a text item to the header control
-- after the `insert_item_item_no' item.
require
exists: exists
label_not_void: a_label /= Void
insert_after_item_no_positive: insert_after_item_no >= 0
local
hd_item: WEL_HD_ITEM
do
create hd_item.make
hd_item.set_text (a_label)
hd_item.set_width (a_width)
hd_item.set_format (a_format)
insert_header_item (hd_item, insert_after_item_no)
ensure
item_count_increased: item_count = old item_count + 1
end
Using this example, let's identify and discuss the different sections.
===Header Comment===
-- Insert a text item to the header control
-- after the `insert_item_item_no' item.
Although the feature's header comment is optional, most Eiffel programmers and their technical managers feel that it should never be omitted. Header comments are included by some language processing tools in specialized views of classes. Header comments are almost always short, usually no more than a few lines. Header comments serve to provide a natural language description of what the feature will do (in the case of features that are commands) or what information it provides (in the case of queries).
===Obsolete===
The feature insert_text_header_item
is not obsolete ... but if it were it would include an obsolete
clause. This works much like the obsolete part for classes that we saw in [[Eiffel Classes|Eiffel Classes]] : the keyword "obsolete
" followed by a manifest string which bears a message to potential reuse consumers:
===Precondition===
require
exists: exists
label_not_void: a_label /= Void
insert_after_item_no_positive: insert_after_item_no >= 0
The precondition part of a feature is introduced by the keyword "require
". It contains a set of assertions which define the state necessary for the correct execution of a routine. We will see more about assertions in [[Design by Contract and Assertions|Design by Contract and Assertions]] .
===Local Declarations===
local
hd_item: WEL_HD_ITEM
This part contains the declarations for any "local entities" used by the feature. Sometimes the computation accomplished in a feature requires the use of entities which are only temporary. It would not be appropriate to make these attributes of the class. So, instead we can use local entities, which have scope only within the feature in which they are declared. In the example, hd_item
is available as type WEL_HD_ITEM
during the computation of feature insert_text_header_item
.
{{note|A local entity name must not be the same as any feature of the class in which its feature occurs or the same as any argument name of the feature in which it occurs. }}
===Routine Body===
do
create hd_item.make
hd_item.set_text (a_label)
hd_item.set_width (a_width)
hd_item.set_format (a_format)
insert_header_item (hd_item, insert_after_item_no)
This is the routine body for a fairly typical effective, internal routine. It contains the instructions that get the job done for the feature. You may notice that experienced Eiffel programmers rarely write routine bodies that are more than a few lines long. The reason for this is more that just intuitive. This phenomenon will be explained in the section [[Design by Contract and Assertions|Design by Contract and Assertions]] .
There are other forms that a routine body can take. Here are some examples of some others:
====External Routines====
cwin_tooltips_class: POINTER
external
"C [macro ] : EIF_POINTER"
alias
"TOOLTIPS_CLASS"
end
The routine body above is for an "external" routine. External routines are used to represent within Eiffel classes, routines that are written in other languages.
{{tip|Because of the high degree of language interaction provided by Microsoft.NET, it is not necessary in Eiffel for.NET to use externals to use software components from.NET assemblies. Instead, these components are presented to the Eiffel programmer as if they were Eiffel classes. Read more about this in [[Conventions|Conventions]] . }}
====Once Routines====
once
Result := some_computation
This is the routine body of a "once" routine, specifically a "once function". A once routine is introduced by the keyword "once
" rather than do
. It contains an computational body that executes only the first time it is called. Upon subsequent calls it has no effect.
Once procedures are useful for doing things that for some reason should not be done multiple times.
If the once routine is a function (as the example above), it computes the resulting object on the first call, then on subsequent calls, it returns the object it originally created without executing the computation. Once functions facilitate shared objects which helps us in avoiding global entities. A class containing a once function can be inherited by many other classes. The first object to apply the once function causes the resulting object to be created and initialized. Subsequent applications by any other object accesses the originally created instance.
====Deferred Routines====
deferred
The body for a deferred routine is simply the keyword "deferred
". Below is the deferred routine body in the context of an entire feature.
set_position (new_position: INTEGER)
-- Set `position' with `new_position'
require
exists: exists
valid_minimum: new_position >= minimum
valid_maximum: new_position <= maximum
deferred
ensure
position_set: position = new_position
end
As we learned in [[Eiffel Classes|Eiffel Classes]] a deferred routine has specification, but no implementation. We will investigate deferred classes and features further in [[Inheritance|Inheritance]] .
===Postcondition===
ensure
item_count_increased: item_count = old item_count + 1
The postcondition part of a routine is introduced by the keyword "ensure
". The postcondition is a group of assertions which describe the state that must be satisfied upon the successful completion of the routine. We will see more about assertions in [[Design by Contract and Assertions|Design by Contract and Assertions]] .
===Rescue===
Our example feature does not have an explicitly coded rescue part. The rescue, introduced by the keyword "rescue
", provides a routine with a chance to do additional processing in the case that it incurs an exeption during normal processing. We will learn about the rescue clause in the section [[Exception Mechanism|Exception Mechanism]] . Until then, you can see what a rescue part looks like in the feature below.
bind
-- Bind socket to local address in `address'.
require
socket_exists: exists
valid_local_address: address /= Void
local
ext: ANY
retried: BOOLEAN
do
if not retried then
ext := address.socket_address
c_bind (descriptor, $ext, address.count)
is_open_read := True
end
rescue
if not assertion_violation then
is_open_read := False
retried := True
retry
end
end
==Making Features Available to Clients==
Remember that when we build classes in Eiffel, we keep in mind the possibility that these classes may eventually become valued, reusable software components. As a result we have a responsibility to make sure that our classes allow clients to use them in a safe and productive fashion. We make available those features that it is appropriate for clients to use, and we hide the rest. In Eiffel we say that a feature that is available to clients is "exported".
Control of the export of features inherited from ancestors is done by using the "export" keyword. Export of inherited features will be discussed in Inheritance.
Control of the export of immediate features (those features introduced in the text of a class) is done with the "feature" clause. We have encountered the feature clause already:
feature -- Access
an_attribute: INTEGER
-- An attribute of type INTEGER
another_attribute: INTEGER = 46
-- A constant attribute
a_function: STRING
-- A function without arguments
do
Result := an_attribute.out
end
another_function (an_int: INTEGER): INTEGER
-- A function with arguments
do
Result := an_attribute + an_int
end
feature -- Basic Operations
a_procedure
-- A procedure with no arguments
do
an_attribute := an_attribute + 5
end
another_procedure (an_int: INTEGER; another_int: INTEGER)
-- A procedure with arguments
do
an_attribute := an_attribute + an_int + another_int
end
A feature clause like the ones in the example above means that all the features that follow, until the next feature clause are exported to clients based on any class. Technically, this
feature -- Basic Operations
is equivalent to
feature {ANY} -- Basic Operations
which means that the following features are available to clients which conform to class ANY
. And in Eiffel, ANY
is the class from which all other classes inherit. As a consequence all classes conform to ANY
.
Inside the braces is a list of classes which are eligible as clients.
feature {STRING_HANDLER} -- Implementation
Features following this example from class STRING
will be available to client class STRING_HANDLER
and all its proper descendants.
As stated above, you can put a list of class names in the braces:
feature {CLASS_A, CLASS_B, CLASS_C} -- Semi-private features
Often features which are solely for implementation should not be seen or used by clients of any type. You can ensure this by exporting to class NONE
, the class from which no other class can inherit:
feature {NONE} -- Implementation
==Eiffel Instructions and Control Structures==
When you begin to write routines in Eiffel, you will need to understand how to write instructions. Fortunately, the set of instruction types you can code is fairly small. Here we will look the most common of these. You will see some more in other topics.
===Creation===
create hd_item.make
We discussed creation procedures and the process of bringing new objects into being in [[Eiffel Classes|Eiffel Classes]] . A creation instruction starts with the keyword "create
". It creates a new object, initialize its fields, may apply a creation procedure, and attaches the object to an entity.
===Procedure Call===
hd_item.set_text (a_label)
The application of a feature to an object constitutes an instruction if the feature is a procedure.
===Assignment===
In Eiffel, assignment syntax is simple. But depending upon the types involved, what actually happens may need some explanation. Assume here that is_open_read
is an attribute declared as type BOOLEAN
:
is_open_read := False
In this instruction, an attribute of type BOOLEAN
named is_open_read
is being assigned the value of the manifest boolean "False
". The attribute is_open_read
is based on an expanded class BOOLEAN
, so that means that the value for False
is held in the field for is_open_read
. This is in contrast to what happens with reference types.
my_string := some_other_string
In this assignment, we will assume that both entities are type STRING
. Because STRING
is a reference type, the field for my_string
will hold either a reference to an instance of STRING
, or it will be Void
. When the assignment above executes, then whatever was in the field for my_string
is replaced with a reference to the same object that some_other_string
refers to.
In summary, for objects based on expanded classes, assignment means assignment of value. For objects based on reference classes, assignment means assignment of reference.
====Conformance====
There is an important rule about assignment in Eiffel. The rule exists to ensure type safety. Consider the following assignment which we would read as "x
receives y
" or "x
gets y
".
x := y
then the rule says that this assignment is valid only if the type of y
conforms to the type of x
.
The rule is clear enough ... if you know what "conform" means. In the presence of mechanisms like expanded types, inheritance, and genericity, a precise definition of conformance is lengthy.
But we can get by for a while with much less. For now let us be satisfied with this:
Consider the classes in the declarations for x
and y
.
You might be tempted to say that to achieve "conformance", the classes would have to be the same. And for the case in which the declarations for x
and y
are expanded classes, you'd be correct. They conform only if they are the same class.
But for reference types, that constraint is unnecessarily restrictive. So, for reference types, the class of y
conforms to that x
if it is the same class or a proper descendant of that class. This facilitates a powerful idea known as polymorphic attachment that we will see more closely in the section on [[Inheritance|Inheritance]] .
===Conditional===
if l_c.is_digit then
l_state := 2
elseif l_c = '-' or l_c = '+' then
l_state := 1
else
l_state := 3
end
Conditionals in Eiffel use at a minimum the keywords "if
", "then
", and "end
". Optionally, they may use the keywords "elseif
" and "else
".
===Loop===
from
i := lower
until
i > upper
loop
if item (i) /= Void and then v.is_equal (item (i)) then
Result := Result + 1
end
i := i + 1
end
There is only one structure for loops in Eiffel. In its typical form it uses the four keywords "from
", "until
", "loop
", and "end
". The instructions following the from
keyword do any initialization necessary for the loop. After until
is a boolean expression which is the exit condition. The loop body after loop
is a list of instructions that are executed for every iteration. As you can imagine, something in the loop body should most likely cause the exit condition eventually to become true.