This site's content was compiled from 1993 to 2006. Beyond that, Google is your friend.
Perhaps the distinguishing feature of object-oriented languages—after classes—to many people is inheritance. Simply put, inheritance is the ability to create a new class from an existing class. It only makes sense to do this when the existing class provides features wanted in the new class.
Where features are not exactly what is required in the new class, they can be redefined. Redefinition can take two forms—either redefinition of an operation or function, or redefinition of type. Redefinition of operation is only meaningful where the feature is a routine. Redefinition of type can be applied to fields, functions, and arguments.
In order to build new classes out of existing classes and to reuse features already defined in those classes, you use inheritance. In Eiffel, you use the inheritance clause as follows:
deferred class VEHICLE feature velocity: INTEGER is -- a function do Result := speed end wheels: INTEGER is deferred end speed: INTEGER stop is -- a procedure deferred end end -- VEHICLE class CAR inherit VEHICLE feature colour: COLOUR -- a field wheels: INTEGER is 4 -- a constant speed: INTEGER stop is -- a procedure do speed := 0 end end -- CAR
This is a very simple example of inheritance—it only shows single inheritance. Eiffel has multiple inheritance. As those who have used a language with multiple inheritance know, if two features with the same name are inherited from two different classes, a clash occurs. Eiffel solves this by having a rename clause that allows you to rename one or both of the features to remove the clash. This is different to the scope resolution operator :: of C++, where disambiguation must be done on every reference to the clashing inherited members.
The example also does not show how to redefine a feature. If you wish to redefine a feature, you must put a redefine clause in your inheritance clause. The example also shows deferred features. You do not have to put a redefine clause in order to give these a definition in a subclass. Defining a deferred feature is called effecting that feature. Our example shows two deferred features, stop and wheels. Note that stop is effected as a routine, whereas wheels is effected as a constant.
Apart from rename and redefine clauses in the inheritance clause, you can change the export status of inherited features with the export clause (you should not use the export clause to restrict access that was previously granted in a parent—that would be mean, but it has some negative theoretical type considerations). Two other clauses that give complete control over inheritance are the undefine and select clauses. Thus, when inheriting any class, you can control the inheritance with the five subclauses: rename, export, undefine, redefine and select.
Many object-oriented languages do not provide redefinition of types and thus are no variant, but this means that many things that can be easily achieved by simple type redefinition are not possible and must be done by providing extra code and classes. In languages that have redefinition of types, the redefinition can be covariant or contravariant. Covariant means the new type must be a subtype of the original type; contravariant means the new type must be a supertype of the original.
Covariant redefinition of fields and functions provides no problems, but covariant redefinition of arguments does create a problem that illegal types can be passed as arguments. However, this is the most practical way to redefine types. Consider that the type of a field can be changed in a subclass:
a_field: A_TYPE set_a_field (to_field: A_TYPE) is do a_field := to_field end
in a subclass these could be redefined as follows:
a_field: B_TYPE set_a_field (to_field: B_TYPE) is do a_field := to_field end
Note that the type of the to_field argument has followed the type of the type of the field. This sets the practical precedent for covariants of arguments. Now Eiffel does even better than this—in the second class, only the type of the argument has changed but the code is exactly the same and needlessly repeated. To deal with this very common situation, Eiffel introduces anchored types:
a_field: A_TYPE set_a_field (to_field: like a_field) is do a_field := to_field end
and in the subclass all you need to do is redefine the field’s type:
a_field: B_TYPE
and no redefinition of the set routine is needed. Using anchored types, most uses of covariant arguments can be avoided. Another point in Eiffel is that accessor or get_ routines need not be provided since fields can be accessed functionally by the principle of uniform access. C programmers argue that this breaks the principle of data hiding—in C accessing fields directly exposes the structure of the class, but in Eiffel it does not, the principle of data hiding being subsumed by the cleaner principle of uniform access.
The other important facility that Eiffel provides is multiple inheritance. Most people understand the use of single inheritance where a subclass gains characteristics from a single parent class. In some cases this can be generalised to multiple inheritance where a subclass can gain characteristics from multiple classes.
Some people see problems with multiple inheritance since classes designed in a single inheritance environment tend to become large and unwieldy, covering multiple concepts. Multiple inheritance allows finer-grained design of classes with a single concept per class which can be combined to form new classes. Thus much more powerful object-oriented design can be accomplished with multiple inheritance.
A problem that multiple inheritance introduces in ambiguity. Suppose the same feature is inherited from two or more classes with slightly different implementations. Which is the correct one to invoke when a client calls it?
Eiffel resolves all such ambiguities in its inheritance clause, by renaming and selecting inherited features. For example:
class PLANE inherit VEHICLE rename drive as taxi end FLYING_OBJECT select move end feature ... end -- PLANE
The inheritance clause in Eiffel can also be used to export existing features to more classes, undefine features, and redefine features.
Since ambiguity is completely resolved within the inheritance clause in Eiffel, this is a fundamentally different approach to C++, where the client programmer must be aware of the nature of multiple inheritance in a class being used and resolve the ambiguity in every access to that class. If the class changes then the potential to have to change all the client classes exists. Because of this messiness in C++, many people believe that multiple inheritance is per se messy, but this messiness does not exist in Eiffel—any headaches in resolving problems due to multiple inheritance must be handled by the creator of the class, not left to clients.
Another aspect of inheritance is that either interfaces or implementations, or both can be inherited. In languages such as Java, only interfaces can be multiply inherited—implementations can be inherited only from one class. Java does this in order to avoid the ambiguity problem that we have seen is elegantly solved in Eiffel.
The problem with Java’s scheme of restricting inheritance of implementation is that interfaces must be implemented in the classes that inherit them. This gives the possibility that the same code must be implemented in many subclasses. Often just a default implementation is required between many classes, and this can be put in an appropriate ancestor class with implementation inheritance. Thus full multiple inheritance of implementation is required for practical programming—arguments against it are purely theoretical.
In Eiffel, there is no separate concept for interface as in Java, everything is included in the class concept. To overcome some of the shortcomings of not having true multiple inheritance in Java, inner classes can be used to provide default implementations, but this is just adding complexity in other areas. In Eiffel, if you are really convinced that single inheritance of implementation and only interfaces can be multiply inherited, you can program in this style—you won’t want to follow that rule for long before it becomes apparent that Java’s interface style of multiple inheritance is a burdensome restriction.