10.13.3.2 Multiple Inheritance

It is possible for a class to be defined with more than one superclass. Because the class inherits properties from multiple superclasses, this is referred to as multiple inheritance.

Multiple inheritance is a complex and controversial topic. What should be done about conflicting slot or method definitions? (This is sometimes called a “name clash.”) What should be done about slots that are inherited from two or more superclasses, but that originate with a common ancestor class? (This is sometimes called “repeated inheritance”.) Different systems take different approaches.

SICStus Objects supports multiple inheritance in a limited but still useful way. It does not allow repeated inheritance, and it places all the responsibility for resolving name clashes on the programmer. This section describes the multiple inheritance features of SICStus Objects.

Class Definitions

The definition of a class with multiple superclasses begins with a class/1 directive of the form

     :- class ClassName = [SlotDef, ...] + SuperClass + ....

The list of slot descriptions and the superclasses to the right of the ‘=’ can appear in any order, without changing the class being defined. In fact, the slot descriptions can be partitioned into more than one list, without changing the class. However, it is best to adopt a fairly simple style of writing class definition and use it consistently.

Just as the slot names in a list of slot descriptions must be distinct, superclass names should not be repeated.

Slots

In SICStus Objects, the programmer has no control over multiple inheritance of slots. All slots from all superclasses are inherited. And, the superclasses should have no slot names in common.

As a consequence, in SICStus Objects no superclasses of a class should have a common ancestor. The only exception would be the unusual case where that common ancestor has no slots.

Methods

By default, all methods are inherited from all superclasses. Any of the superclasses' methods can be uninherited, as described earlier, by using the uninherit/1 directive.

If the same message is defined for more than one superclass, however, you must choose at most one method to inherit for the message. You may choose none. You may do this by defining a new method for the message (shadowing the superclasses' methods), or by using the uninherit/1 directive, or by using the inherit/1 directive.

The following is considered a classic example of multiple inheritance.

     :- class toy.             % no slots in this class
     
     Self >> size(small).
     
     Self >> rolls(false).
     
     :- end_class toy.
     
     :- class truck.         % no slots in this class
     
     Self >> size(large).
     
     Self >> rolls(true).
     
     :- end_class truck.

The idea expressed in these definitions is that most toys are small and do not roll. On the other hand, most trucks are large, but they do roll. A toy truck shares one feature with each class, but we can hardly expect a compiler to choose the correct one.

The definition of a new class, toy_truck, might begin with

     :- class toy_truck = toy + truck.

Rather than redefine the get methods for size and rolls, we can specify which to inherit in two ways. One way is positive, stating which to inherit, and the other way is negative, stating which not to inherit.

The positive version would be

     :- inherit
             toy >> (size/1),
             truck >> (rolls/1).

This is more convenient when a message is defined in several superclasses, because all but the chosen method are uninherited. And, it is probably easier to understand.

The negative version would be

     :- uninherit
             toy >> (rolls/1),
             truck >> (size/1).

The toy_truck class would exhibit the same behavior with either definition.

It is possible to define methods that access the shadowed or uninherited methods of the superclasses, by sending the message to the superclasses. In the case of multiple inheritance, however, it may be necessary to specify which superclass to send the message to.

The toy_truck class, for example, might define these methods:

     Self >> uninherited_size(S) :-
             super(truck) >> size(S).
     
     Self >> uninherited_rolls(R) :-
             super(toy) >> rolls(R).

They provide access to the unchosen methods from toy_truck's superclasses.

While these examples with the toy_truck class are clearly “toy” examples, the same techniques can be used in more realistic cases.

Abstract and Mixin Classes

While SICStus Objects only supports a limited form of multiple inheritance, its facilities are sufficient for working with so-called mixin classes.

The idea is to construct similar classes by first defining a class that contains the things the desired classes have in common. Typically, this will be an abstract class, which will have no instances itself. Then, provide the features that differentiate the desired classes with a set of mixin classes

Mixin classes that have nothing in common can safely be mixed together, to build the desired classes. The mixin classes will usually be abstract classes, also, because they are too specialized for their instances to be useful on their own.

The date_stamp class defined earlier would make a good mixin class. A similar time_stamp class might be (partially) defined as follows:

     :- class time_stamp =
             [hour:integer,
              minute:integer,
              second:integer].
     
     Self <- create :-
             time(time(Hour, Minute, Second)),
             store_slot(hour, Hour),
             store_slot(minute, Minute),
             store_slot(second, Second).

Another mixin class might be used to “register” objects in the Prolog database.

     :- class registry = [name:atom].
     
     Self <- create(Name) :-
             Self << name(Name),
             assert(registered(Name, Self)).
     
     Self <- destroy :-
             Self >> name(Name),
             retract(registered(Name, Self)).

The registry mixin class could have been used with the point class to define the named_point class, which was an example from an earlier section.

The ability to send a message to an object's superclass is useful when working with mixin classes. Suppose the definition of a new class begins with

     :- NewClass = OldClass + date + time + registry.

where OldClass is some previously defined class that lacks the features provided by the date, time and registry classes. (In fact, they should not have any slot names in common.) Then its create method can be defined by

     Self <- create(Name) :-
             super(OldClass) <- create,
             super(date) <- create,
             super(time) <- create,
             super(registry) <- create(Name).

This avoids the need to duplicate the code in the create methods of OldClass and all three mixin classes.


Send feedback on this subject.