David L. Shang
INDEX
Almost all complex systems in the world involves inheritance in which objects can be classified in a class hierarchy. Objects that are instances of a class are also instances of all ancestors of that class, as it is shown in the biological classification, an instance of a Mammal class is also an instance of the Animal class.
In practice, classification cannot always be disjoint. Two classes can have objects in common. Also, two or more independent class hierarchies may coexist for different purposes. The class Aquatic may contain objects that are also instances of the class Mammal. Animals can be classified by their habitats, food, or kinds.
When we are interested in common objects of two or more classes, we then need a subclass that inherits all the common attributes specified by these classes. Most object-oriented programming languages incorporate the concept of multiple inheritance in order to model this overlap in classification.
Bertrand Meyer gives multiple inheritance a good name--the marriage of convenience:
It is like a marriage uniting a rich family and a noble family. The bride belongs to the aristocratic family: she brings prestigious reputation (interface) but no practical wealth (implementation). The groom comes from a well-to-do bourgeois family, needing some luster reputation to match his practical wealth. The two make a perfect one.
Without multiple inheritance, the marriage could not be fair. Making either a bride or a groom in a subordinate status by aggregation can only result a discriminative tragedy. Simulating multiple inheritance by aggregation or parameterization is often counter-intuitive and inconvenient, because aggregation and parameterization deal with completely different patterns.
Multiple inheritance, on the other hand, significantly increases the complexity of the class system. The complexity comes from the duplication of the superclass. If both Aquatic and Mammal are subclasses of Animal that has a method of SpontaneousMove, should an instance of AquaticMammal have duplicated Animal bodies with different SpontaneousMove methods? With the method overriding mechanism, the situation is even more complex. The method SpontaneousMove of an AquaticMammal could be in one of the many different situations depending on whether the Animal inherited by Aquatic is the same Animal inherited by Mammal, and whether the method SpontaneousMove is re-implemented in Aquatic, Mammal, and/or AquaticMammal.
C++ fully supports multiple inheritance. A superclass class part can either be duplicated or shared in subclasses with multiple inheritance.
Inheritance is by nature an is-a relation. Duplication of superclasses is rarely useful. When both Aquatic and Mammal are an Animal, there are seldom chances that Aquatic and Mammal don't share Animal. The difference can always be made by extension and overridding mechanism. Thus we shouldn't make a AquaticMammal of two separate Animal bodies. Imagine that we have an AquaticMammal with two Animal bodies. When the method of SpontaneousMove--no matter where it is implemented--is called, which animal body should be moved? the Aquatic's animal body, or the Mammal's animal body, or both? It is rather confusing.
Unfortunately, C++ makes superclass duplication a default choice: if the superclass is not specified as virtual, it is by default duplicated in subclasses that multiple-inherit the superclass.
With the belief that multiple inheritance generates more problems than benefits, the designers of Java discarded multiple inheritance. The desirable feature of multiple inheritance is provided by multiple interface implementation. A Java interface is a class that has nothing but abstract methods (pure virtual functions) and constants. A subclass can be extended from a single base (super) class as well as implement more than one interfaces.
The designers of Transframe hold a similar belief but provided a rather different approach which avoided all the problems with multiple inheritance and result a more powerful feature than Java's multiple interface implementation.
Similar to Java's interface class, Transframe has an equivalent concept in which only deferred member classes (member classes without implementations) and meta members (members that have class scope) can be defined. A subclass can be derived from more than one superclasses, but only the first superclass can be non-interface class. The first superclass is called primary superclass.
Since interface classes do not have method implementations and data members (fields), superclasses duplication is no longer necessary. They are always shared. The complexity of multiple inheritance found in C++ has been removed. We often call this mechanism multiple interface inheritance, or interface mix-ins
On the other hand, multiple interface inheritance disables implementation mix-ins, which is sometimes desired to be able to inherit implemented member functions from more than one superclasses.
Consider a C++ example that uses the Observer pattern given by Robert Martin (Java and C++: A Critical Comparison):
1 class Observer 2 { 3 public: virtual void Update() =0; 4 }; 5 6 class Observable 7 { 8 private: SetmyObservers; 9 public: void Register(Observer& x) { myObservers.Add(&x); }; 10 void Notify() 11 { 12 for (Iterator x(myObservers); x; x++) 13 (*x)->Update(); 14 }; 15 }; 16 17 class Clock 18 { 19 private: Time myTime; 20 public: virtual void Tick() { myTime++; }; 21 }; 22 23 class ObservableClock: public Clock, public Observable 24 { 25 public: void Tick{} { Clock::Tick(); Notify(); }; 26 }; The class ObservableClock is a combination of a clock and an observed object. We would like to use multiple inheritance to obtain the implemented methods from both the Clock and Observableclasses.
With Java's multiple interface inheritance, one have to simulate this by aggregation, which is inconvenient and counter-intuitive:
1 public interface Observer 2 { 3 void Update(); 4 }; 5 6 public interface Observable 7 { 8 void Register(Observer x); 9 void Notify(); 10 }; 11 12 class ObservableImp extends Observable 13 { 14 private Vector myObservers; 15 public void Register(Observer x) { myObservers.AddElement(x); }; 16 public void Notify() 17 { 18 Enumeration e = myObservers.elements(); 19 while (e.hasMoreElements()) 20 { 21 Observer x = (Observer) (e.nextElement()); 22 x.Update(); 23 } 24 }; 25 }; 26 27 class Clock 28 { 29 private Time myTime; 30 public void Tick() { myTime.increaseOneSecond(); }; 31 }; 32 33 class ObservableClock extends Clock implements Observable 34 { 35 private ObservableImp myObservableImp; 36 public void Register(Observer x) { myObservableImp.Register(x);}; 37 public void Notify() { myObservableImp.Notify(); }; 38 public void Tick{} { super.Tick(); Notify(); }; 39 };Though Transframe's interface class cannot have implemented member classes, an interface class can be inherited with a delegation to its default implementation. A delegation is a subclass of an interface class. It implements the member class declared in the interface class and is used for the default implementation of the member class declared in the interface class.
When a delegation is provided to a subclass, the subclass shall have an implicitly declared member object of the type of the delegation. This member object is called delegate member. The member class in the interface superclass is inherited as if it were overridden by the delegate member class. However, its owner shall be automatically delegated to the delegate object when the member class is used for object instantiation.
Consider the Transframe's version of the ObservableClock example:
1 interface class Observer 2 { 3 public: function Update(); 4 }; 5 6 interface class Observable 7 { 8 public: function Register(Observer); 9 function Notify(); 10 }; 11 12 class ObservableImp is Observable 13 { 14 private: myObservers: list of Observer; 15 public: function Register(x: Observer) { myObservers.Add(x); }; 16 function Notify(){ foreach (x in myObservers) x.Update(); }; 17 }; 18 19 class Clock 20 { 21 private: myTime: Time; 22 public: function Tick() { myTime++; }; 23 }; 24 25 class ObservableClock is Clock, Observable by ObservableImp 26 { 27 public: function Tick{} { Clock::Tick(); Notify(); }; 28 };A call to the member function Notify or Register declared in the interface Observable via an observable clock will be delegated automatically to the implementation provided in the class ObservableImp via delegate object--an instance of ObservableImp.
Note that ObservableClock is a subclass of Observable, but not a subclass of ObservableImp.
Another con against multiple inheritance as well as inheritance is the that inheritance exposes the information to the users who does not have the right to use them, which violates the principle of information hiding.
Though C++ and Java provide accessibility control such as private and protect to prohibit unauthorized users from using the private or protected features, it is still an annoying fact that each class must present a complete specification in order to be inherited by subclasses, though most of the specification may be useless to users.
This defect not only complicates the interface of class with a bunch of useless information, but also adds a heavy burden, making the support of dynamic programming (scripting and dynamic design) efficiently imppossible: the dynamic programming environment must load a complete semantic description for all existing classes in use.
Transframe's interface mix-in plus implementation delegation forms a perfect information hiding mechanism. The interface defines only the methods that are publically accessible. The class that implements the interface class are not necessarily to be fully defined before it can be used in delegation (In C++ and Java, a class must be fully defined before it can be used in inheritance.)
Back to the Observer pattern. The implementation of Observable can be hidden in a separate package and never exposed to users. What follows is the interface of the Observer pattern presented in a Transframe's definition package:
def object ObservePattern // a definition package (singleton) { interface class Observer { function Update(); } interface class Observable { function Register(Observer); function Notify(); } Observable ObservableSingle (); Observable ObservableMultiple (number_of_observers: int); }Within the pattern, two implementations of Observable are provided: ObservableSingle is an implementation that supports only one observer; and ObservableMultiple is a subclass of Observable that can be observed by more than one observers. The actual implementations will be presented in a separate implementation package:
imp object ObservePattern // a implementation module (singleton) { class ObservableSingle is Observable () { private: myObserver: Observer = null; public: function Register(x: Observer) { myObserver:=x; }; function Notify(){ if (myObserver) myObserver.Update(); }; } class ObservableMultiple is Observable (number_of_observers: int) { private: myObservers: Observer...; public: function Register(x: Observer) { myObservers.Add(x); }; function Notify(){ foreach (x in myObservers) x.Update(); }; enter (number_of_observers: int) { myObservers := (Observer...number_of_observers)(); } } }Users are presented with the definition package only. They can select one of the implementations for a subclass construction without knowing the detail of the implementation:
with ObservePattern use all; object MyPackage { class Clock { private: myTime: Time; public: function Tick() { myTime++; }; } class ObservableClock is Clock, Observable by ObservableMultiple { public: function Tick{} { Clock::Tick(); Notify(); }; enter (): ObservableMultiple(5) {}; } }Applications are not subjected to change by the modification of the implementation package. This provides a good mechanism for plug-in software components.
With multiple inheritance, it is possible for a subclass to inherit more than one member functions (classes) with the same name and the same input interface type (signature).
Consider the following C++ example:
1 class Watchable 2 { 3 public: virtual SizeType Size () =0; 4 virtual void DrawSelf () =0; 5 }; 6 1 class Locatable 2 { 3 public: virtual PositionType Position () =0; 4 virtual SizeType Size () =0; 5 };Both the class Watchable and Locatable has a member function Size. If a subclass is derived from them and declares the member function:
6 class DisplayableWidget: public Watchable, public Locatable 7 { 8 public: SizeType Size() { // return the size of the Widget; } 9 };This member function will implement the Size function defined in both Watchable and Locatable.
Java and Transframe has the same override mechanism as C++ in this case. However, the following Java example will show the difference from C++:
1 interface Watchable 2 { 3 SizeType Size (); 4 void DrawSelf (); 5 }; 6 1 interface Locatable 2 { 3 PositionType Position (); 4 SizeType Size (); 5 }; 6 7 class Widget 8 { 9 public: SizeType Size () { // return the size of the Widget; } 10 }; 11 12 class DisplayableWidget extends Widget implements Watchable, Locatable 13 { 14 };Although the member function Size is not declared in the class DisplayableWidget, it is implemented in the class Widget which is inherited by DisplayableWidget. Therefore, the implementation provided in the class Widget is automatically used to implement the Size method declared in both Watchable and Locatable.
With C++, DisplayableWidget must explicitly declare the member function Size in order to implement the Size function declared in these two interface classes:
15 class DisplayableWidget: 16 public Widget, public Watchable, public Locatable 17 { 18 public: SizeType Size () { return Widget::Size(); } 19 };Transframe has the same override mechanism as Java in the second case. However, the following Transframe example will show the difference from Java and C++:
1 interface class Watchable 2 { 3 public: function Size (): (float;float); 4 function DrawSelf (); 5 }; 6 1 interface class Locatable 2 { 3 public: function Position (): (float;float); 4 function Size (): (float, float); 5 }; 6 1 interface class Containable 2 { 3 public: function GetContents (): Widget...; 4 function Size (): (float, float); 5 }; 6 7 class Widget 8 { 9 public: SizeType Size () { // return the size of the Widget; } 10 }; 11 12 class DisplayableWidget is Widget, Watchable, Locatable, Containable 13 { 14 public: 15 function Containable::Size(): (float;float) 16 { 17 widgets: Widget... = GetContents(); 18 size: (w: float; h: float) = (0,0); 19 widget_size: (w: float; h:float); 20 foreach (widget in widgets) 21 { 22 widget_size:=widget.Size(); 23 size.w := max (size.w, widget_size.w); 24 size.h := max (size.h, widget_size.h); 25 } 26 return size; 27 } 28 };Like Java, the implementation provided in the class Widget automatically implements the Size function declared in both Watchable and Locatable. However, the Size function declared in the class Containable is specifically overridden in the subclass.
This specifically overridden function must be called by using the class scope resolutor. Consider:
1 x: DisplayableWidget(); 2 x.Size(); 3 x.Containable::Size();The first Size function call uses the implementation provided in the class Widget; and the second uses the one implemented in the class DisplayableWidget specifically for the Containable size.
Without a renaming feature, we feel that this specific override mechanism is necessary. When interface classes are designed separately in different domains, we cannot guarantee that all the member classes (functions) with the same name and the same input signature will have the same semantics.
The Transframe's inheritance mechanism is more general than C++ and Java because the concept of member function is generalized to member classes.
A member class can inherit another member class as shown in the following figure:
![]()
Suppose we want to develop a task model which is a set of resources shared by a number of concurrent threads. Intuitively, we would like to have Thread defined as a member class of the Task class:
1 class Task 2 { 3 public: 4 class Thread 5 { 6 public: 7 auto enter () 8 { ... 9 Append(self); 10 ... 11 } 12 }; 13 function Append(Thread); 14 };Using this high level task model, one can develop well-structured tasks as shown in the following example:
15 class BallFactory is Task 16 { 17 private: 18 ball_storage: Ball[]; 19 public: 20 class Produce is Thread 21 { 22 public: 23 enter (produce_speed: int) { ... }; 24 }; 25 class Consume: public Thread 26 { 27 public: 28 enter (consume_speed: int) { ... };; 29 }; 30 };Member classes Produce and Consume are specific threads derived from Thread. They are members of BallFactory and can be used in the same way as an C++ member function is used:
31 factory: BallFactory(); // a ball factory is created 32 factory.Produce(5); // a produce thread is created with a rate 5 balls per sec. 33 factory.Produce(5); // another produce thread is created with a rate 5 balls per sec. 34 factory.Produce(10); // a consume thread is created with a rate 10 balls per sec.In C++, a class may also be defined within another class. This class is called a nested class. The C++ nested class is a pseudo member class because it cannot use any members except for type names and static names declared in its enclosing class. It is not a real member because it cannot be used in the same way as other members (e.g. member functions) are used.
Therefore, the above task model cannot be written simply in C++. The nested class Thread has no access to member functions defined in Task. As result, a task reference must be provided explicitly when a thread is created:
1 class Task 2 { 3 public: 4 class Thread 5 { 6 public: 7 Thread (Task* task) 8 { ... 9 task->Append(this); 10 ... 11 } 12 }; 13 ... 14 }; 15 16 class BallFactory: public Task 17 { 18 public: 19 class Produce: public Task::Thread 20 { 21 public: 22 Produce (BallFactory* factory, int produce_speed): 23 Task::Thread(factory) 24 { 25 ... 26 } 27 }; 28 ... 29 }; 30 31 BallFactory factory; 32 BallFactory::Produce(factory, 5); 33 ...Nested classes are only syntax sugar in C++ and they are actually treated as global classes. Note that at line 19, the base class Thread must be qualified by the class name Task as if it were not inherited. At line 23, the thread's constructor must be called explicitly c++ while Transframe provides automatic constructors that subclasses are not required to call it explicitly. This inheritance pattern in Transframe is often used by high-level model design, for examples: distributed server with remote services, active object with communication ports, and databases with transactions.
![]()
C++ nested classes does not support overriding as discussed in the nest section. Java does not support nested classes.
The Transframe's overriding mechanism is more general than C++ and Java. In general, the implementation of a member class (not necessarily to be limited to member functions) can be overridden. Consider the following example:
1 class ListWindow is Window (name: char[]) 2 { 3 class Item is Window(name: char[]) 4 { 5 enter (name:char[]): Window (name, ListWindow::self) {}; 6 }; 7 };where Item is a member class of ListWindow. Now, we consider a subclass:
1 class IconList is ListWindow (char[]) 2 { 3 class Item is Window(name: char[]) 4 { 5 enter (name: char[]): Window (name, IconList::self) 6 { 7 display (DefaultIcon(name)); 8 }; 9 }; 10 };where the member class Item's constructor body with the interface (char[]) has been overridden. Consider the following usage:
1 function CreateListWindow 2 (LWT:type of ListWindow; wn:char[]; name_list:char[]...):LWT; 3 { 4 list_window: LWT(wn); // create a parent list window 5 foreach (name in name_list) 6 list_window.Item(name); // create a child window 7 return list_window; 8 }If we have a function call:
1 CreateListWindow(IconList,"File List","a.txt","b.jpg","c.ps");The actual constructor (enter body) executed for the object instantiation by the expression list_window.Item(name) is the one defined in the class IconList. As a result, three items will be created as child windows with their default icons displayed.When another specific list window (subclass of list window) is developed later, say, BrowserList, the generic function CreateListWindow can be used without any modification or recompilation:
1 CreateListWindow(BrowserList,"File List","a.txt","b.jpg","c.ps");
The following diagram illustrates the difference in the concept of multiple inheritance among languages of Transframe, Java, and C++.
Transframe Java C++ Concept simple simple duplication conflict Mix-ins interface & imple- interface mix-ins interface & imple- mentation mix-ins only mentation mix-ins Information genuine pretended pretended Hiding hiding hiding hiding Multiple support default support default not supported Override overrider overrider support specific not supported not supported overrider Member Class true member not supported pseudo nested Inheritance class inheritance class inheritance Member Class supported not supported not supported Override