Multiple inheritance refers to the ability of a class to have more than a single superclass. Various object-oriented languages provide multiple inheritance; equally many languages provide only single inheritance, possibly with `interface inheritance' constructs like Objective-C's protocols and Java's interfaces. C++ at one point in its committee life, in all its baroqueness, had both multiple inheritance, plus inheritance of interface through signatures.
Suppose the class D has both B and C as a superclass.
implementation class D: B, C end; implementation instance D end; |
What effect does this inheritance of two classes have?
Any state defined for instances of B or C is also present in instances of D. There is no sharing of slots based on the name of instance variables as in CLOS. Thus, every instance variable i consuming space in an instance of B or C, also consumes space in an instance of D.
Every method defined for B is also defined for D. Obviously, every method defined for C is also defined for D.
If both B and C define the same method foo, a method clash is said to have occured, and D should provide its own implementation of that method. This is not mandatory; it is not checked by the compiler; it is optionally checked by the resolver. If a method clash is not resolved, a program-condition is raised when the method is invoked at run time.
A class with instances that carry state (i.e., instance variables) must be a subclass of the State class. If both B and C maintain state, they must both inherit from State, which brings up the issues involving repeated inheritance. Actually, these issues are mild in TOM when compared to the same issues in languages like C++ or Eiffel. To sum it up: repeated inheritance is shared inheritance.
With respect to instance variables, things remain the same: every instance variable declared in a superclass gets its spot in the subclass. Thus, D only has one isa instance variable (inherited from State), even though it `is inherited twice'.
If a method foo is defined by State, unharmed by B, and redefined by C, it is the redefinition of C that is applicable to D. The implementation by State that `is visible' through the inheritance of B is nulled by it being overriden in the inheritance path through C.
Messaging super is performing an invocation of a method as provided by a superclass. Usually, the method is invoked from the method that overrides the original definition. For example, the following is not uncommon for an initializer:
id init { my_counter = 1; = [super init]; } |
If a class has multiple superclasses, the message to super must indicate which superclass is to provide the method implementation. If the super message is unambiguous, the compiler will make the obvious choice, that can be described as follows: Suppose class D overrides the method init as described above. Imagine a class E, with exactly the same superclasses as D, but without overriding any method. Then, invoking init on an instance of E will be bound to a particular method implementation. If that implementation is provided by a direct superclass of E or is only visible through a single direct superclass of E, than that is the superclass a message to super in D refers to. If, however, there is a method clash, or the method is visible through more than one direct superclass, the super reference is ambiguous and must be disambiguated, as shown in this example (note that the syntax of directing which super to message is different from `casting super'):
id init { my_counter = 1; = [super (B) init]; } |
Net effect is that when dynamic loading introduces an init method for instances of class C, that change will not be applicable to this method and its messaging of super.