18.5.1 Use of a mix-in class

In our airport example, four classes now define slots that serve as names or strings that represent identifiers for objects:

define abstract class <vehicle-storage> (<physical-object>)
  slot identifier :: <string>, required-init-keyword: id:;
  ...
end class <vehicle-storage>;
define abstract class <vehicle> (<physical-object>)
  slot vehicle-id :: <string>, required-init-keyword: id:;
  ...
end class <vehicle>;
define class <airport> (<physical-object>)
  slot name :: <string>, init-keyword: name:;
  ...
end class <airport>;
define class <airline> (<object>)
  slot name :: <string>, required-init-keyword: name:;
  ...
end class <airline>;

Our example would be more unified and maintainable if we had a single representation for these identifiers.

There are several ways that we could improve the example using single inheritance. One way to do that in principle would be to define a name slot in a common superclass. In this case, we cannot use this solution, because the only common superclass is the built-in class <object>. This approach would work if all named classes inherited from <physical-object> — we could add a name slot to <physical-object>. But then all subclasses of <physical-object> would inherit the name slot, whether or not those subclasses need names. Some objects might be inappropriately named, and those instances would be larger than they need to be.

Another approach would be to define two new subclasses to contain the name slot: a <named-object> subclass of <object>, and a <named-physical-object> subclass of <physical-object>. We would then use <named-physical-object> as the superclass for <vehicle-storage>, <vehicle>, and <airport>, and we would use <named-object> as the superclass for <airline>. That would work, too, although the name slot would be defined in two classes, rather than in one.

Suppose, however, that we later find that some, but not all, subclasses need another attribute, such as a unique identifier. Perhaps <airport>, <vehicle>, and <airline> need unique identifiers, but <vehicle-storage> does not. Extending this model, we might have to define new classes <unique-object>, <unique-named-object>, <unique-physical-object>, and <unique-named-physical-object>. We now have eight base classes to represent the possible combinations of name and unique identifier. If we add a third attribute, we end up with many more classes. We soon have an unmanageable proliferation of base classes.

Multiple inheritance provides a solution to these problems. We can define a mix-in class, name-mix-in, whose only purpose is to contain the name slot:

define abstract class <name-mix-in> (<object>)
  slot name :: <string>, init-keyword: name:;
end class <name-mix-in>;

Now, we redefine our <vehicle-storage>, <vehicle>, <airport>, and <airline> classes to have two direct superclasses: <name-mix-in>, and either <object> or <physical-object>:

define abstract class <vehicle-storage> (<name-mix-in>, <physical-object>)
  // identifier slot removed
  required keyword name:;
  ...
end class <vehicle-storage>;
define abstract class <vehicle> (<name-mix-in>, <physical-object>)
  // vehicle-id slot removed
  required keyword name:;
  ...
end class <vehicle>;
define class <airport> (<name-mix-in>, <physical-object>)
  // name slot removed
  keyword name:, init-value: "Anonymous Airport";
  ...
end class <airport>;
define class <airline> (<name-mix-in>, <object>)
  // name slot removed
  required keyword name:;
  ...
end class <airline>;

We use the required keyword option to make the name: keyword required when we create an instance of <vehicle-storage>, <vehicle>, or <airline>. If we provided an init-value: or init-function: for the name slot in the definition of <name-mix-in>, Dylan would ignore that option when we created an instance of any of these subclasses.

We also use the keyword option with an init-value: to provide a default initial value for the name: initialization argument and for the name slot for instances of <airport>.

Of course, we also have to change other code in our example to use the name name and the init keyword name: when referring to the slot.

Multiple inheritance provides several advantages in solving the name problem:

1. We localize in a single class the characteristic of having a name.

2. Subclasses can still customize aspects of the name attribute, such as what that attribute's initial value is, and whether or not it is required.

3. We can give a subclass a name attribute without redefining any of its superclasses.

4. The only subclasses that have a name attribute are those for which that is appropriate.