5.6 Interface Sharing




Interface sharing refers to the definition of methods in a base class that are guaranteed to be available in all derived classes. These methods define a shared interface in the sense that all objects of derived classes can be manipulated through this common (or shared) interface even if the user of the object is not aware of the object's exact type. Methods defined in the base class, of course, are part of this shared interface. However, a "guarantee" can be given in a base class even when the method is implemented only in the derived classes. In this way, interface sharing is similar to type casting, but it is more powerful because it allows derived class methods to be used in addition to base class methods.

The Clock class is an example of a class where interface sharing is needed. From the point of view of a Clock object the Counter and Cycler classes must be treated as two distinct classes. Despite the code sharing in the Counter and Cycler classes, their "types" are not similar enough for them to be treated interchangeably or uniformly by the Clock class. This implies that the Clock class must have distinct data members and overloaded methods to interact with both Counter and Cycler objects. The relevant code for the Clock class is:

Clock Class

   class Clock {
    private:
	Counter* counter;	// when Connect(ed)To a Counter   
	Cycler*  cycler;	// when Connect(ed)To a Cycler   
	...
    public:
	...
	ConnectTo(Counter& cnt);
	ConnectTo(Cycler&  cnt);
	...
   };

   void Clock::ConnectTo(Counter& cnt) { counter = &cnt;);
   void Clock::ConnectTo(Cycler&  cnt) { cycler  = &cnt;);
   void Clock::Notify() { if (counter) counter->Next(); else
                          if (cycler)  cycler->Next();   }

Not only are the Counter and Cycler classes distinct types, but all of the many subclasses of DisplayableNumber are distinct. The deal with all of these subclasses the Clock class would need numerous pointer variables, numerous overloaded ConnectTo methods, and repeated code in the Notify method as follows:

Repetitive Code in the Clock Class

   class Clock {
    private:
	Counter*        counter;	// when Connect(ed)To a Counter   
	Cycler*         cycler;		// when Connect(ed)To a Cycler   
	BiCycler*       biCycler;	// when Connect(ed)To a BiCycler
	UpCounter*      upCounter;	// when Connect(ed)To a UpCounter     
	DownCounter*    downCounter;	// when Connect(ed)To a DownCounter    
	BatchCounter*   batchCounter;	// when Connect(ed)To a BatchCounter  
	JumpCounter*    jumpCounter;	// when Connect(ed)To a JumpCounter  
	SwitchCounter*  switchCounter;	// when Connect(ed)To a SwitchCounter  

	...
    public:
	...
	ConnectTo(Counter&        cnt);
	ConnectTo(Cycler&         cnt);
	ConnectTo(BiCycler&       cnt);
	ConnectTo(UpCounter&      cnt);
	ConnectTo(DownCounter&    cnt);
	ConnectTo(BatchCounter&   cnt);
	ConnectTo(JumpCounter&    cnt);
	ConnectTo(SwitchCounter&  cnt);
	...
   };

   void Clock::ConnectTo(Counter&   cnt) { counter   = &cnt;);
   void Clock::ConnectTo(Cycler&    cnt) { cycler    = &cnt;);
   void Clock::ConnectTo(BiCycler&  cnt) { biCycler  = &cnt;);
   void Clock::ConnectTo(UpCounter& cnt) { upCounter = &cnt;);

   ... // rest of ConnectTo methods for other subclasses 

   void Clock::Notify() { if (counter) counter->Next(); else
                          if (cycler)  cycler->Next();  else
                          if (biCycler) biCycler->Next(); else
                          if (upCounter) upCounter->Next(); else
                          ... // similar tests for other subclasses   
                         }

For designing real system such a situtation is not tolerable because:

For building more efficient and flexible systems, another technique must be used.

The specific problem is that the Clock class uses a method (Next) defined in the various derived classes (Counter, Cycler, etc.) but not defined in their base class (DisplayableNumber). As was seen with the NumberPanel, it is possible through type casting for the Clock class to use any of the methods defined in the base class even though the actual object is a Counter or Cycler. Type casting will not suffice, however, when the method to be called is not in the class to which the actual object is cast. For example, type casting a Counter object to a DisplayableNumber yields an interface that does not include the Next method.

Virtual Methods

The shared interface is defined by declaring one or more methods to be "virtual" or "pure virtual". For a "virtual" method, the base class provides a default definition of the method. In some cases, the default definition may be a "do nothing" or "null" method - a method that takes no actions. A "pure virtual" method is one where the base class does not provide a default method definition. In this case, the responsibility for providing the required method implementation is passed to the derived class.

A "virtual" method in a base class allows an overriding derived class method to be called even if the caller is only aware of the base class type. In our example, the DisplayableNumber class would define a virtual Next method with an empty (null, do nothing) implementation. Each derived class would then override the "virtual" method with the "real" method - the "real" method would provide the implementation that is appropriate for the derived class. A Clock object would then be able to call the Next method of a DisplayableNumber derived class object even if (and especially if) the exact type of the called object is unknown by the Clock object.

The DisplayableNumber class with a virtual Next method would be declared as follows:

	class DisplayableNumber {
	private:
	   ...
	public:
	   ...
		virtual void Next();		// virtual Next method
	   ...
	};

	void DisplayableNumber::Next() {}	// null default definition
The Counter class, and all other subclasses of DisplayableNumber, remain unchanged. Each of these subclasses contains an overriding Next method.

The Clock class can now be redeclared to take advantage of the shared interface. The important part of this redefinition is as follows:

	class Clock {
	private:
		DisplayableNumber *number;	// only know about base class
	public:

		void ConnectTo(DisplayableNumber* dn);
		void Notify();

	};

	void Clock::ConnectTo (DisplayableNumber* dn) { number = dn; }

	void Clock::Notify() { 
		number->Next();		// invokes derived class method
		number->Show();		// invokes  base class method
	}
The important things to notice are that:

This solves the three problems noted above, namely: as the number of subclasses of DisplayableNumber increases, the Clock class remains unchanged, the repetitive source and object code is eliminated, and the execution time of the Notify method is constant.

The revised Clock and DisplayableNumber classes can be used as follows:

	Clock   oneMinute(60*1000), 
                oneSecond(1000);
	Counter minutes(0);
	Cycler  seconds(60);

	oneMinute.ConnectTo( (DisplayableNumber*) &minutes);
	oneSecond.ConnectTo( (DisplayableNumber*) &seconds);

	oneMinute.Start();
	oneSecond.Start();

In this example two different derived class objects are passed as arguments to the ConnectTo method. In one case the object is a Counter object and in the other case the object is a Cycler object. However, because of the shared interface defined by the virtual Next method, the Clock class is able to handle uniformly all objects derived from the DisplayableNumber class.

Pure Virtual Methods

In some cases a default implementation cannot reasonably be given in a base class. It is, perhaps, questionable whether it is a good design decision to provide an empty Next method in the DisplayableNumber class. Providing a class with a Next method suggests that something will happen to advance the state of the object when the method is invoked. The fact that nothing happens may reasonably be viewed as counterintuitive or misleading.

A "pure virtual" method declares a method that must be implemented in a derived class because no implementation of the virtual method is given in the base class. A pure virtual method is used when:


a pure virtual method is a virtual method since the derived class method is used when the pure virtual method is invoked.

The syntax for declaring a pure virtual method is shown in the following redefinition of the DisplayableNumber class. To avoid confusion this new class is called a AbstractDisplayableNumber.

	class AbstractDisplayableNumber {
	...
	public:
		virtual void Next() = 0;	// pure virtual method
	...
	};
The additional syntax "= 0" is used to denote a pure virtual method. The intuition behind this syntax is somewhat strained - it may simply have to be committed to memory.

A class containing one or more pure virtual method is referred to as an "abstract base class". The word "abstract" is used to suggest that the class can describe a wide range of possible objects depending on how the pure virtual methods are implemented by the derived classes. In contrast, the term "concrete class" is sometimes used to refer to a class that does not have pure virtual methods or has given implementations for all such methods. This class is concrete because it denotes a fixed, specific type of object. It is not possible to create objects of abstract classes. For example, the following code would not be allowed:

	AbstractDisplayableNumber adn(100);    // not allowed

This restriction is how the enforcement is done of the "guarantee" that a pure virtual method will be implemented by a derived class. If a derived class does not implement all of the pure virtual methods that it inherits, then it is aslo considered an abstract class. Thus, objects with unimplemented pure virtual methods can not be created. This restriction is reasonable because an abstract base class is incomplete, it itself does not provide the implementation of its pure virtual methods.

It is possible, and often done, to have pointers to abstract base classes. Such pointers are needed in order to be able to refer to a derived class object without without knowing its exact type. For example, suppose that a class is derived from AbstractDisplayableNumber as follows:

	class ConcreteCounter : public AbstractDisplayableNumber {
	...
	};

	void ConcreteCounter::Next() { ...some implementation ... }
The following examples illustrate what can and can not be done with abstract classes and pointers to abstract classes.

   AbstractDisplayableNumber *adn;	// ok - pointer to abstract class
   ConcreteCounter           *cc;	// ok - pointer to concrete class

   AbstractDisplayableNumber n(100);	// no - class is abstract
   ConcreteCounter c(100);		// ok - instance of concrete class

   cc = &c;				// ok - types are the same

   adn = &c;				// ok 
The last line of code above ("adn = &c;") is meaningful because it allows a concrete object (i.e., an object of a concrete class) to be manipulated only by knowing its abstract base class.

Manipulating an object of a concrete class through a pointer to its abstract base class is both useful and safe. It is useful because it allows very flexible systems to be invented; the system can manipulated a wide variety of concrete objects knowing only their general (abstract class) properties. It is also safe to manipulate objects in this way because the pure virtual methods are guaranteed to be implemented in the concrete object - that is what makes them concrete. For example, the usage:


   AbstractDisplayableNumber *adn;	
   ConcreteCounter c(100);		

   adn = &c;				

   adn->Next();

is always safe in that the Next method is guaranteed to be implemented in whatever concrete object is pointed to by the pointer "adn".

Polymorphism

The term "polymorphism" is used to describe how objects of different classes can be manipulated uniformly. The word polymorphism is derived from the words "poly" indicating "many" or "various" and "morph" which refers to "shape" or "form". Through polymorphic techniques it is possible to write code (such as the Clock class above) that manipulates "many" different "forms" (subclasses of DisplayableNumber) of objects in a uniform and consistent manner without regard for their individual differences. The flexibility and generality of polymorphic structures is one of the significant advantages of object-oriented programming. Learning how to recognize the opportunity for exploiting this possibility takes practice.

The strategy for developing a polymorphic structure begins by identifying the common methods across a group of similar but not identical types of objects. A class hierarchy is defined in which the common methods are placed in a base class while the remaining methods are organized into classes derived from this base class. The interface of the base class defines the "shared interface" through which an object of any of the particular subclasses can be manipulated. It is important to remember that while methods of the shared interface must be declared in the base class they may be left abstract with the subclasses assuming the responsibility for providing the definition (code) for the abstract methods.

The general class structure for using polymorphism is shown in the figure below. The base class contains one or more virtual functions (vm1, vm2, vm3) that represent the shared interface through which the objects of the derived classes can be manipulated. The virtual functions may be given default implementations in the base class or they may be defined as pure virtual functions. These virtual functions are defined or overridden (if necessary) in the base classes. If the base class methods are pure virtual then the methods must be defined in the derived classes. If the base class methods are virtual, but not pure virtual, then the methods may be overridden in the derived classes.

Class Structure for Polymorphism

Several examples of polymorphism will be given. To stress the importance and generality of this concept, the examples are taken from class hierarchies that are implemented in different object-oriented programming languages. Only the high-level structure of the classes is presented so that the important point is not obscured by the differences in syntax among the various languages.

One example of polymorphism can be found in the Abstact Windowing Toolkit (AWT) class hierarchy. This class hierarchy is part of the Java run-time environment and the classes are written in Java. A portion of the AWT class hierarchy is shown below. The Component class is the base class for numerous derived classes, only some of which are shown in the following figure. This base class represents the shared interface of a range of different entities that can appear in a user interface built with Java and the AWT.

The Component Class Hierarchy

The Component class includes a method inside(int, int) that determines if the point defined by the two intenger input parameters lies "inside" the given component. This method is useful in determining if the user has just clicked on a button (check if the current coordinates of the mouse are inside a button when a click event occurs). Exactly how a component checks for "inside" will depend on the nature and geometry of the component. Nonetheless, it is meaningful to inquire of any component whether the coordinates are inside of that component.

Using the polymorphism enabled by the Component class, the essential pieces of a simple ComponentManager are shown below. The ComponentManger is reneder here in C++. The critical element of this ComponentManager is that it can manipulate any of the subclasses of Component. When the inside method of the ComponentManager is invoked, it in turn queries the Component objects that it knows about in search of one that contains the coordinates (x,y). If such a Component is found, then a pointer to that Component is returned otherwise a NULL value is returned indicating that the point does not lie inside of the Components managed by the ComponentManager.

Use of Polymorphism: Component Example

   class ComponentManager {
     private:
        Component *member[10];
        int        number;
        ...
     public:

         ComponentManager() : number(0) {}
         void Enroll(Component *cmp) { member[number++] = cmp; }   
         Component* inside(int x, int y) {
                 for(int next = 0;  next<number; next++) 
                    if (member[next]->inside(x,y) )
                        return member[next];
                 return NULL;
         }
         ...
    };
   

Static and Dynamic Binding

The term "dynamic binding" is used to describe the mechanics of virtual functions. Binding refers to the act of selecting which body of code will be executed in response to a function invocation. In languages like "C", the binding is done completely at compile time. Even with overloaded methods, the binding is done completely at compile time. Binding that is done at compile time is also called static binding. It is static in the sense that one set, it does not change. However, with virtual methods, the binding can not be done completely at compile time because the actual body of code to execute when a virtual function is called depends on the actual class of the object, something that is not known at compile time. To see the need for binding being made at run-time, consider the Clock class defined above, a portion of this is repeated here:

	class Clock {
	private:
		DisplayableNumber *number;	// only know about base class
	public:

		void Notify();
	};

	void Clock::Notify() { 
		number->Next();		// invokes derived class method
		...
	}
The binding for the invocation of the Next method in the Notify method cannot be done at compile time because the compiler does not know the actual class of the object being pointed to by the pointer variable number. In fact, there is no single binding that will suffice as different Clock objects may be bound to different subclasses of DisplayableNumber. What the compiler does instead is to build an efficient way of performing the binding at run-time.

Exercises

None as yet.

Last Updated: March 18, 1996 / kafura@cs.vt.edu