Multiple inheritance is still controversial in the object-oriented community. While notable languages such as C++ and Eiffel embrace the feature, it has been rejected by other significant languages, including Modula-3, Objective C and Java™.
We survey the advantages and disadvantages of multiple inheritance, as well as techniques that are often cited as alternatives. Also, we present a language feature called automated delegation, which offers many of the advantages of multiple inheritance, as well as several additional benefits, while avoiding the principal drawbacks associated with multiple inheritance. Finally, we present Jamie, a preprocessor-based implementation of automated delegation for the Java™ programming language.
I would like to thank Reimer Behrends for his thorough critiques of my ideas, and for many discussions that had a positive impact on this work. Many others provided me with useful feedback on this work, for which I am grateful, including John Regehr, Tim Hollebeek, Anand Natrajan, Steve MacDonald, Paul Reynolds and the people at Reliable Software Technologies.
Special thanks go to Paul Reynolds, for all of the advice, support, opportunity and friendship he has given me.
Finally, I would like to thank my wife and family for enduring my time in school with me.
Abstract i
Acknowledgments ii
Table of contents iii
Chapter 1: Introduction 1
1.1 Thesis 1
1.2 Motivation 1
1.3 Related work 3
1.4 Contributions 4
1.5 Terminology 5
1.5.1 Subclassing 5
1.5.2 Subtyping 6
1.5.3 Inheritance 7
1.5.4 Delegation 7
1.6 Organization 8
Chapter 2: Advantages of Multiple Inheritance 10
2.1 Multiple Specialization 10
2.2 Mixin inheritance 11
2.3 Multiple subtyping 12
2.4 Pairing interfaces and implementations 13
2.5 Debatable examples 14
2.5.1 Implementation Inheritance 14
2.5.2 Facility inheritance 15
2.6 Summary 15
Chapter 3: Problems with Multiple Inheritance 16
3.1 Name conflicts 16
3.2 Repeated inheritance 18
3.3 Misuse 19
3.4 Obscurity 19
3.5 Summary 19
Chapter 4: Partial Solutions 21
4.1 Interfaces 21
4.2 Copy and modify 23
4.3 Base class modification 24
4.4 By-reference solutions 25
4.5 Delegation 26
4.6 Summary 28
Chapter 5: Automated Delegation with Jamie 29
5.1 Basic forwarding with Jamie 30
5.2 Exclusion 32
5.3 Dynamic features 33
5.4 Delegating to static methods 36
5.5 Summary 37
Chapter 6: Design of Automated Delegation 38
6.1 Syntax 38
6.2 Granularity 39
6.3 The evolution of the forwarder keyword 40
6.3.1 The “caller” keyword 40
6.3.2 The “owner” keyword 41
6.3.3 Early versions of “forwarder” 41
6.4 Modifiers 42
6.4.1 Visibility modifiers 42
6.4.2 Other modifiers 43
6.5 Universal base classes 43
6.6 Summary 44
Chapter 7: Implementation of Jamie 45
7.1 Overview 45
7.1.1 The forwards to clause 45
7.1.2 The forwarder implements clause 47
7.1.3 The forwarder keyword 50
7.2 Static methods 51
7.3 Why a preprocessor? 53
7.4 Additional features 53
7.5 Summary 54
Chapter 8: Analysis of Automated Delegation 55
8.1 Automated Delegation vs. Multiple Inheritance 55
8.2 Drawbacks of automated delegation 58
8.3 Automated Delegation vs. Single Inheritance 60
8.4 Delegation in languages with multiple inheritance 61
8.5 Summary 62
Chapter 9: Related Work 63
9.1 Classless languages 63
9.2 Hybrid Models 64
9.2.1 Object Specialization 64
9.2.2 C++ 65
9.2.3 Smalltalk-style forwarding methods 66
9.2.4 Transframe 68
9.3 Language support for mixins 70
9.3.1 Parametric polymorphism 70
9.3.2 Mixin construct 71
9.4 Dynamic subclassing 72
9.4.1 Dynamic inheritance in Self 72
9.4.2 The become: message in Smalltalk-80 72
9.4.3 Predicate classes in Cecil 73
9.4.4 Declarative specialization in Java 74
9.5 Summary 74
Chapter 10: Conclusions and Future Work 75
10.1 Conclusions 75
10.2 Future
Work 76
References 77
Appendix A 81
In this thesis, the benefits and drawbacks of multiple inheritance are enumerated. It is then shown how automated delegation achieves a superset of the benefits multiple inheritance provides while avoiding the principal drawbacks.
This thesis also presents Jamie, an implementation of automated delegation that brings the benefits of multiple subclassing to the Java™ programming language.
In languages with inheritance, a language feature that automates delegation, when coupled with a subtyping mechanism such as Java’s interfaces, can be a suitable replacement for multiple inheritance, providing not only most of the benefits that are traditionally found in multiple inheritance, but also many additional benefits. Also, automated delegation can avoid the principal drawbacks of multiple inheritance, namely repeated inheritance, misuse and unclear behavior.
Consider writing in Java™ a class SortableVector that can be sorted by invoking a sort method. The preliminary design that suggests itself is to have a class SortableVector, which would inherit from class Vector as well as from class SortableCollection. It seems an appropriate design, since a sortable vector “IS-A” vector, and it “IS-A” sortable collection as well. Since Java does not provide multiple inheritance, the programmer must find an alternative implementation.
In order to reuse sorting code, one may keep it separate from the vector code. However, assuming the sorting code depends on some standard container functions implemented in the Vector class in order to perform the sorting, there would still need to be some communication between the sorting code and the Vector class. A common technique programmers use in such situations is delegation, which is familiar to many of those who program in Objective C [Cox84] and Java. Delegation is a technique where the programmer instantiates an object and forwards method calls from one class (called the delegator, or the forwarder) to the instantiated object (called the delegate). For example, here is a possible implementation of a SortableVector in Java:
class SortableVector extends Vector
{
private SortableCollection sort_helper;
SortableVector()
{
// Pass "this" to the delegate’s constructor so it
// can perform sorting operations on this instance.
sort_helper = new SortableCollection(this);
}
void sort() throws SortingError
{
sort_helper.sort();
}
}
In the above code, the sole responsibility of the method sort is to delegate the job of sorting to sort_helper (such a method is called a forwarding function).
Writing such code is a burden on the programmer, especially as interfaces get large. He must remember to mirror the return type, argument list and throws clause when appropriate. He must make sure to either return a value or not as appropriate. Then, if the class he is using as a delegate changes, he must update his delegating object appropriately.
Suppose the programmer wants to limit the objects that can be inserted into the vector to be of type String. Since Java offers no form of parametric polymorphism to provide this kind of type safety, he probably would change the methods by which the programmer may add data so that they would only accept data if the arguments are strings, leaving the other methods untouched. If using the Vector class in the standard Java library, the programmer would want to override three methods, and to reuse the implementation of twenty-one methods [CLK98]. Unfortunately, simple inheritance does not permit such reuse in Java, since all of the methods of class Vector are final, which means they may not be redefined in a subclass. Thus, the programmer would either need to write his own vector implementation, or delegate to twenty-one methods.
While there may be valid reasons for not providing multiple inheritance in a language, situations like the above would be less problematic for the programmer if multiple inheritance were present. However, the work a programmer does may be automated, either by a language or by a language preprocessor.
Work related to automated delegation can be divided into language features that attempt to offer similar functionality and ad hoc strategies for simulating such functionality.
Ad hoc techniques that are employed to simulate multiple inheritance include code duplication techniques, reference passing and delegation. These solutions are presented in Chapter 4.
Other languages offer alternatives to inheritance for either multiple subclassing or for dynamically changing inherited method implementations (dynamic subclassing). Such work is presented in Chapter 9.
This work makes three contributions to the object oriented programming language community:
1. A survey and critique of subclassing techniques: This thesis both surveys and critiques previous multiple subclassing techniques including multiple inheritance, manual delegation and such language features as Predicate Classes in Cecil [Cha93]. For each of these techniques, the advantages and disadvantages are weighed, both from the programmer’s perspective, and, where appropriate, from the language designer’s perspective. No similar survey of subclassing techniques has previously appeared in the literature.
2. Automated delegation: This thesis introduces and defines a language feature called automated delegation, which is a multiple subclassing feature similar to multiple inheritance. Automated delegation is also be compared to multiple inheritance, and is shown to have the following advantages:
· Automated delegation is a more appropriate abstraction than is multiple inheritance for most of the scenarios in which multiple inheritance is generally considered appropriate. For example, implementation inheritance and facility inheritance (discussed in Chapter 2) are naturally supported by automated delegation, yet are often criticized uses of multiple inheritance.
· Automated delegation is more expressive than is multiple inheritance. For example, it can be used to support dynamically changing implementations, which is not possible with multiple inheritance.
· Automated delegation does not suffer from the principal drawbacks of multiple inheritance. In particular, repeated inheritance and unclear behavior (obscure code) are avoided.
3. Multiple subclassing for the Java programming language: This thesis presents Jamie, which is a preprocessor based extension to Java that implements automated delegation. For each aspect of Jamie’s design and implementation, the important design and implementation choices that were made are presented, as will be the effects those choices had on the final product. In cases where a different solution may be desirable in future systems, the ramifications of such alternatives are discussed.
This section defines the terms that are most important to the rest of this work. Most of these terms are in common use within the object-oriented community, yet have no universally accepted definition.
Subclassing is the derivation of methods and possibly variables from another class. For example, inheritance, as is found in C++ [Str97], is a subclassing mechanism, since it allows the user to use methods from another class.
A subclassing relationship does not imply that type inheritance exists. For example, Sather [Omo93] allows the programmer to use implementations as a kind of code inclusion; the subclassing mechanism does not perform type inheritance under any circumstances.
The implementation is unimportant, as long as the programmer may reuse code. For example, delegation is a form of subclassing, even though delegation usually uses a class instance to achieve reuse, as opposed to sharing the blueprint of a class.
Given a class X, a subclass of X is any class Y that performs subclassing in order to derive methods or data from class X. In such a case, X is considered to be a superclass of Y.
Multiple subclassing is directly subclassing from multiple (potentially unrelated) classes at once. A multiple subclassing mechanism should allow the programmer to subclass from an arbitrary number of classes at the same time.
A language feature that allows the programmer to subclass directly from an arbitrary number of classes is said to enable multiple subclassing, whereas a feature that limits that number is said to provide limited multiple subclassing.
While a class may be allowed to subclass from more than one class indirectly, a feature is not said to enable multiple subclassing unless the programmer may subclass from unrelated objects.
Subtyping is the ability for a data type (usually a class) to derive its type from another data type. An instance of the derived type may be substituted for an instance of the base type, although the reverse may not always be true. If class Y is a subtype of class X, then class Y must share class X's signature. In other words, class Y's methods and data must be a superset of those provided by class X. In the Java programming language [AG96], there is a special type of class that is a signature completely devoid of implementation, called an interface. A Java interface consists only of method signatures; data may not be part of an interface[1]. In addition to subtyping through inheritance, a Java, class may also subtype by implementing an interface, which is explicitly declaring that the class provides implementations for all of the methods in the interface. A class is said to fulfill an interface if it provides implementations for all of the methods in an interface. A class may fulfill an interface without implementing it.
Multiple subtyping is the ability to subtype from multiple classes at the same time. In Java, the programmer may subtype as many times as he wishes, as long as he does so via the interface mechanism.
There is no universally accepted definition for inheritance.