Adding Class Features

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 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 : 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 .

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 .

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:

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 : 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 .

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 .

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 .

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 a deferred routine has specification, but no implementation. We will investigate deferred classes and features further in 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 .

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 . 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, thisfeature -- Basic Operations

is equivalent tofeature {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 . 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 .

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.

cached: 03/19/2024 12:34:43.000 AM