Keywords: multiple inherit, join, undefine, repeated inherit, select
Multiple and repeated inheritance are presented in this chapter. A class
may inherit from one or many parents. A common pattern is to have two features
with the same name and signature, where one is deferred and defines the
interface, and the other, effective feature defines the action. These features
are automatically merged or joined during inheritance. If both features
are effective, one version may be converted to a deferred feature by undefine,
so the two features are automatically joined. With multiple inheritance,
there may be a name clash in the inherited features, that is resolved by
rename,
undefine,
or by a clause that tells the class to
select a particular feature
as the active feature. A class may inherit from the same parent one or
more times, showing repeated inheritance.
A class may inherit from more than one parent, and the child can use features from both parents. This is a very common practice in OO systems, because it allows each class to define a constellation of useful features, that a child class can inherit and combine. Multiple inheritance is implemented by listing multiple class names in the child's inherit clause, such as
class X
inherit
A; B; C
The inherited class names are separated by semi-colons; if there are sub-clauses within each class, then these are listed within each class, such as
class X
inherit
A
rename
p as q,
r as s
redefine
t
end;
B;
C;
With multiple inheritance, all the features from all the parents are now features of the child. While the code to inherit multiple classes is simple, care must be taken if there are two or more inherited features with the same name and signature. If one feature is deferred and one effective, then the two features are automatically joined or merged in the child to define one effective feature. If both features are effective, then the name clash must be resolved.
To illustrate the power of multiple inheritance, this chapter shows how to store a data structure to a file and how to retrieve that data from file. Consider a system that uses complex data structures such as lists. The list is stored in a file when the system is not executing. When the system is executed, it retrieves the file and converts it to a list, then uses or changes the list as neeeded. The (possibly updated) list is then stored to file before the system terminates. The class STORABLE offers features to store an object to and retrieve an object from a file, but that class has nothing to do with lists. The desired class can be defined by inheriting from both class LINKED_LIST, and from class STORABLE, to define a storable list.
The next two sections describe how to store an object in a file and
retrieve it from a file using the classes FILE and STORABLE.
A storable list is then defined by multiple inheritance in the third section
of this chapter.
The main classes in the Eiffel file hierarchy are shown below. Class
FILE
supplies most of the effective features, but it is a deferred class so
you need to use objects of type RAW_FILE.
Care must be taken to separate the ideas of an Eiffel file object, and the physical file itself that is stored on some external storage medium by the operating system. A file object (of type RAW_FILE) has a name and a pointer to the physical file. The name of the file is a STRING. When the file object is created, Eiffel looks in your current directory for a stored file of that name, and if the stored file exists it attaches a pointer from the file object to the stored file. If the file does not exist, then Eiffel does not create a new file; it simply notes that the file does not exist. The only way to create a file is to create a file object and then use the store command on this object to store the data in the object to file.
If a file is to be read from storage, then a file object is created and opened to read data from file. If a file is to be stored, then the file object is opened to write data to file. After the file has been read (written), it should be closed. A much abbreviated short listing of class FILE that provides these features is shown below; the full short listing describes almost 100 features.
deferred class interface FILE
feature -- Initialization
make (fn: STRING)
-- Create file object with fn as file name.
require
string_exists: fn /= void;
string_not_empty: not fn.empty
ensure
file_named: name.is_equal (fn);
file_closed: is_closed
feature -- Status report
exists: BOOLEAN
-- Does physical file exist? (Uses effective UID.)
feature -- Status setting
open_read
-- Open file in read-only mode.
require
is_closed: is_closed
ensure
exists: exists;
open_read: is_open_read
open_write
-- Open file in write-only mode; create it if it does not exist.
ensure
exists: exists;
open_write: is_open_write
close
-- Close file.
ensure
is_closed: is_closed
Older versions of Eiffel used the class UNIX_FILE instead of RAW_FILE, but UNIX_FILE is now an obsolete class. The Eiffel libraries (and the Eiffel language) have grown and changed over the years, so some early features are now obsolete. The Eiffel compiler converts these obsolete features to their modern form, and gives you a warning message so you can change the code at some later time. A warning message is not an error, just a warning. The Eiffel message warning about obsolete classes is
Warning code: Obsolete
Warning: Type relies on obsolete class.
What to do: update to new class at your earliest convenience.
The class is still available,
but may be removed in the future.
The Eiffel Library class STORABLE allows an object to be stored to a file and retrieved from a file. An abbreviated class interface is shown below.
class interface STORABLE
basic_store (file: IO_MEDIUM)
-- Produce on file an external representation of the
-- entire object structure reachable from current object.
-- Retrievable within current system only
require
file_not_void: file /= Void;
file_exists: file.exists;
file_is_open_write: file.is_open_write;
file_is_binary: not file.is_plain_text
general_store (file: IO_MEDIUM)
-- Produce on file an external representation of the
-- entire object structure reachable from current object.
-- Retrievable from other systems for same platform
require
file_not_void: file /= Void;
file_exists: file.exists;
file_is_open_write: file.is_open_write;
file_is_binary: not file.is_plain_text
store_by_name (file_name: STRING)
-- Produce on file called file_name an external
-- representation of the entire object structure
-- reachable from current object.
-- Retrievable from other systems for same platform
require
file_name_not_void: file_name /= Void;
file_name_meaningful: not file_name.empty
retrieve_by_name (file_name: STRING): STORABLE
-- Retrieve object structure, from external
-- representation previously stored in a file
-- called file_name
require
file_name_exists: file_name /= Void;
file_name_meaningful: not file_name.empty
retrieved (file: IO_MEDIUM): STORABLE
-- Retrieved object structure, from external
-- representation previously stored in file.
require
file_not_void: file /= Void;
file_exists: file.exists;
file_is_open_read: file.is_open_read;
file_is_binary: not file.is_plain_text
ensure
result_exists: Result /= Void
end interface -- class STORABLE
The routine headers require an argument of type STRING or IO_MEDIUM; because IO_MEDIUM is a deferred class, an object of type RAW_FILE is actually used.
One way to store and retrieve data is shown below. The first routine creates a file object, and if a physical file with the defined name exists, it opens the file for reading and retrieves the file contents. If no file exists, then a new object is created that will later be stored to file. Conditional assignment is used to retrieve the data, because we only want to attach the object to the identifier if the types are compatible. If the structure of the system has changed between the time the data was stored and the time it is retrieved, then the stored structure no longer matches the defined structure and the assignment attempt will fail.
The second routine writes the object to file. The second routine could use the existing file object instead of creating a new one, but this would add to the number of attributes in the class and so has been avoided.
class X
inherit
STORABLE
feature
p: PERSON
name: STRING is "person.dat"
retrieve
is
-- retrieve the object from file if possible
-- create a new object if there is no file
local
file:
RAW_FILE
do
!!file.make (name)
if file.exists then
file.open_read
p ?= retrieved (file)