More About Classes
Ranga Rodrigo
Copying objects.
Information hiding.
Eiffel class types are
reference types.
Unexpected Phenomena Due to
Reference Types
read_name is
local
first_name, last_name : STRING
do
io.read_string
first_name := io.last_string
io.read_string
last_name := io.last_string
io.put_string(first_name + " " +
last_name + "%N")
end



Issues like this arise particularly when data
values are tested for equality or copied.
All languages provide built-in facilities for
assignment and testing equality, in Eiffel
using the symbols "=", "/=" and ":=".
These operations work on values.
References

In languages like Java and Eiffel, where
class variables hold references, the
operations work on the reference values. In
Eiffel:


Assignment of class variables b := c copies a
reference value, so b and c refer to the same
object.
Equality and inequality tests on class variables b
= c and b /= c compare reference values, and
report on whether b and c refer to the same
object.
In Eiffel


Assignment of class variables b := c copies
a reference value, so b and c refer to the
same object.
Equality and inequality tests on class
variables b = c and b /= c compare
reference values, and report on whether b
and c refer to the same object.
Providing Value-Semantics




Often, however, we want "value" semantics
even with reference variables.
To provide this, features can be defined in
the root class of a language's class hierarchy
which can be overidden in user-defined
classes to provide the required functionality.
In Java, this root class is called "Object" and
in Eiffel "ANY".
It is assumed that every other class defined
in the language automatically inherits from
the root class, even if this is not stated in the
class definition.
Equality

Two objects can be tested for equality using
the following two features, defined in ANY:
b.is_equal(c)
equal(b, c)

These features compare objects on a field by
field basis, and return true if all attributes are
the same in both objects, in the sense of
b.field = c.field. equal(b, c) will work
even if b = Void, so is often less
cumbersome to use than b.is_equal(c),
where you should check for this case, or else
risk a run-time exception.
Equality




Philosophically speaking, this is an odd
sense of equality.
Normally we assume that if two objects are
equal, they can be substituted for each other
in any context without problem.
This is not guaranteed by equal, however.
Consider a POINT class, with two coordinate
attributes, and a LINE class, with two POINT
attributes. Then look at the following code:
origin, p1, p2 : POINT
l1, l2 : LINE
create origin.make(0, 0)
create p1.make(1, 1)
create p2.make(1, 1)
create l1.make(origin, p1)
create l2.make(origin, p2)
equal(p1, p2) -- is True, but
equal(l1, l2) -- is False
Equality


This seems to be a translation into Eiffel of
the assertion "two distinct lines can be drawn
from the origin to a given point", which is
probably not true of the domain being
modelled by the POINT and LINE classes.
The problem here is that equal only tests for
equality of the first-level attributes within two
objects.
Deep Equality


If these attributes hold references, we might
want to check whether their attributes are in
turn equal, rather than just seeing if they are
the same object.
Eiffel has a notion of deep equality which
does this. Using the definitions above:
deep_equal(l1, l2) -- is True
Deep Equality



For some reason, there is no corresponding
feature b.is_deep_equal(c).
Rather than using the notion of deep
equality, a simpler approach is to explicitly
define a suitable equality test for each class.
This is done by redefining the feature
is_equal. (The syntax for inheritance shown
here will be explained in the next lecture.)
class
LINE
inherit
ANY
redefine
is_equal
end
feature
p1, p2 : POINT
is_equal( l : LINE ) : BOOLEAN is
do
Result := equal(p1, l.p1) and
equal(p2, l.p2)
end
Object Copying



Objects can be copied using a feature copy,
overwrites the field values in one object by
those of another, or clone which returns a
new object which is a copy of another.
Shallow copying can lead to multiple
references to shared objects, so deep
equivalents of these features also exist.
The syntax is:
c.copy(b)
c.deep_copy(b)
c := clone(b)
c := deep_clone(b)
Object Copying


Notice that, if no redefinitions are in effect,
two objects will be equal after a copy, and
deep_equal after a deep_copy.
A class-specific copy function can be
obtained by redefining the copy feature
inherited from ANY.
We must limit
the access that client
code has to the
internal details of
a class.
Information Hiding


Information hiding is a technique to limit the
access that client code has to the internal
details of a class.
Benefits of this include:


protection against unintended corruption of
class data stored in attributes;
ability to modify the internal details of a
class without affecting client code
(provided that the class interface is kept
constant).
Information Hiding in Java and C++


Java and C++ provide mechanisms for
defining client access to class features:
public, private etc.
A class will typically support information
hiding by making its attributes private, and
providing a public interface.
Information Hiding in Eiffel


In Eiffel, the Uniform Access Principle and
the fact the clients have read-only access to
class attributes make this approach
unnecessary in simple cases. In general,
however, an information hiding mechanism is
needed.
E.g., the RESULTS class defined in the last
lecture: Eiffel does not prevent client code
from changing the values in the array that
records the points gained for the each game:
r : RESULTS
r.points[2] := 3
class
RESULTS
create
make
feature
points : ARRAY[INTEGER]
played : INTEGER
total : INTEGER
make( games : INTEGER ) is
do
create points.make(1, games)
end
add_result( pts : INTEGER ) is
do
played := played + 1
points.put( pts, played )
total := total + pts
end
end
Information Hiding in Eiffel

The mechanism for supporting information
hiding in Eiffel is to specify which classes
have access to a given feature or features.
The points array can be made private as
follows:
feature {}
-- empty list of clients, so
private
points : ARRAY[INTEGER]
Information Hiding in Eiffel
class
RESULTS
feature {}
-- empty list of clients, so private
points : ARRAY[INTEGER]
feature
-- the default is public
played : INTEGER
total : INTEGER
...
end
Information Hiding in Eiffel



In C++ and Java, a class instance has
access to the private data members of all
other instances of the class.
In Eiffel, however, this is not the case:
Given the class declaration above, the
following code will not compile because the
current instance of the class does not have
access the the private features of the
parameter r, even though it is an instance of
the same class.
Information Hiding in Eiffel
class
points array
points array
of r
of this instance
RESULTS
...
compare( r : RESULTS ) is
do
if r.points[0] > points[0] then
io .put_string("They did
better.")
end
end
end
Information Hiding in Eiffel


In a way this is logical: the declaration
feature {} says "no class has access to
this feature", i.e., not even other instances of
the same class.
To make this example work, you must
explicitly grant this access:
class
RESULTS
feature {RESULTS}
points : ARRAY[INTEGER]
...
end
Information Hiding in Eiffel


An alternative approach would be to leave
the original access level but provide an
access function get_points to use where
direct access to the array is blocked.
An argument can be made that this is
preferable, as it isolates direct access of the
array data structure to one place; if this data
structure was subsequently changed, it
would be simpler to update the class if only
one function made use of it.
Information Hiding in Eiffel


Information hiding is in general provided in
Eiffel by specifying in a feature clause all the
classes that have access to the features in
that clause.
In fact, access is granted not only to the
named class, but also to all of its subclasses.
Information Hiding in Eiffel



To give all classes access to a feature you
can write:
feature {ANY}
because all classes are descendants of the
root class ANY.
This is the default case, and is the equivalent
of public in C++ and Java.
Information Hiding in Eiffel

A declaration like
feature {RESULTS}

gives access not only to the results class but
also all of its subclasses. In other words, this
is similar to defining the feature to be
protected, in C++ and Java.
Information Hiding in Eiffel: Summary
feature
speed : DOUBLE
Read only access to
clients. No write access.
feature {}
speed : DOUBLE
No access to clients,
including the instances of
the same class.
No access except to
CAR and its
descendants (protected).
feature {CAR}
speed : DOUBLE
feature {ANY}
speed : DOUBLE
Access to all the classes
(public).
Information Hiding and DBC



A routine's pre and postcondition form a
contract between the writer of the routine and
its client.
It seems logical then that features that are
mentioned in a routine's specification should
be accessible to potential clients of the
routines; otherwise, the client would be in the
position of not being able to examine the
contract.
Eiffel enforces this requirement for
preconditions. In the RESULTS class, the
following code will not compile:
Information Hiding and Preconditions
points array
not accessible to
clients
add_result( pts : INTEGER ) is
require
points[played + 1] = 0
do
played := played + 1
points.put( pts, played )
total := total + pts
end
Information Hiding and Preconditions


In the previous example, precondition is not
accessible to the client, who is therefore
unable to check that the precondition is
satisfied before calling the routine.
One way round this, if it is a problem, is do
define an accessor function which returns the
data necessary to write the precondition.
Information Hiding and Post-conditions


The situation with postconditions is slightly
different.
Postconditions can mention private features,
as the effect of a routine on such features
can be a crucial part of the routine's
specification:
Information Hiding and Preconditions
add_result( pts : INTEGER ) is
do
played := played + 1
points array
points.put( pts, played
)
is accessible
to
total := total + pts post-condition
ensure
points[played] = pts
end
Information Hiding and Post-Conditions



A postcondition mentioning a private feature
is considered to be a private postcondition,
however, and not part of the routine's
contract.
Warning: it is sometimes stated that private
postconditions do not appear in the interface
view of a class, but this is not consistently
supported by EiffelStudio.
Class invariants are not affected by the
access level of features, as invariants are not
accessible to the clients of a class.
Abstract Classes



These are called deferred in Eiffel.
Both the class and any abstract features
must be explicitly declared to be deferred.
As in other languages, base classes in
hierarchies are often abstract.
Descargar

More About Classes