Multiple Inheritance

A Critical Comparison on Transframe, Java & C++

David L. Shang



INDEX

Pros and Cons

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:

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.

Multiple Inheritance in C++, Java, and Transframe

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:	Set myObservers;
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.

Information Hiding

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.

Override with Multiple Inheritance

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.

Member Class Inheritance

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.

Member Class' Constructor Overriding

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");

Summary

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