5.2 Using Inheritance to Share Implementations

The Counter and Cycler classes represent concepts that are very similar. The Counter defines objects whose internal value is incremented over a range of values. The Cycler class defines objects whose internal values are incremented modulo the Cycler's base value.

The obvious similarities in the Counter and Cycler classes is evident in their definitions:

	class Counter {
	 private:
		int value;
		TextBox* textBox;
	 public:
		Counter(int initValue);		// start at initValue
		Counter();			// start at 0
	   void ShowIn (TextBox& tbox);		// place to display value
           void Show();				// display current value
	   void Next();				// increment
	   void Reset();			// get new value from user
           int  Value();			// reply current value
               ~Counter();			// clean up

	class Cycler {
	 private:
		int value;
		int base;
		TextBox* textBox;
	 public:
		Cycler(int b);			// modulo base b
		Cycler();			// modulo base 10
	   void ShowIn (TextBox& tbox);		// place to display value
	   void Show();				// display current value
	   void Next();				// increment
	   void Reset();			// get new value from user
           int  Value();			// reply current value
               ~Cycler();			// clean up

The similarities among these classes include both the data that each maintains and the code for some of their methods. The similarities are:

	Data:
		value     : an internal integer value
		TextBox*  : a pointer to a TextBox

	Code:
		ShowIn	  : provide a TextBox where value is displayed
                Show      : display the current value in the TextBox
                Reset     : use the TextBox to get new value from user
	        Value     : returns the current internal value

Notice that the Next method is not similar: its implementation is what makes the classes different. Notice also that the constructors and destructors are not similar. Because the class name is used to name the constructor and destructor, it is not possible to have them shared among classes with different names.

The similar parts of the implementations are gathered together into a separate base class. Pictorially the structure to be created is:



The base class, DisplayableNumber, contains all of the shared implementation, both code and data. The two classes, Counter and Cycler, are derived classes. Each derived class inherits from the base class the base class's code and data. Each derived class adds its own code and data specific to the purpose of the derived class.

A derived class can itself be a base class whose implementation may be shared by classes derived from it. In this manner, inheritance extends over several (perhaps many) levels. The example above had only two levels but more extensive use of inheritance to share implementations is possible and even common.

To illustrate multiple levels of inheritance consider a kind of counter, called a JumpCounter, that can be incremented by 1 or by an arbitrary amount. Such a counter might be used to show the current length of a file where the length of the file may be changed by appending a single character (increment by 1) or by appending an entire string (increment by the length of the string). The Counter class has all of the functionality necessary to implement a JumpCounter except the ability to increase the internal value by a given amount.

The inheritance needed to define a JumpCounter is shown in the diagram below where the JumpCounter class is derived from the Counter class. The Counter class is itself derived from the DisplayableNumber class.



The essential point is that a JumpCounter object responds to all methods of its immediate class (JumpCounter) as well as to all of the methods of its ancestor classes (Counter and DisplayableNumber). This is illustrated by the following code:

  JumpCounter  fileLength;
  TextBox      lengthDisplay(Location(100,100), Shape(50,20));
  ...
  fileLength.ShowIn(lengthDisplay);	// uses method in DisplayableNumber
  fileLength.Show(lengthDisplay);	// uses method in DisplayableNumber
  ...
  fileLength.Next();			// add 1 using method in Counter 
  ...
  fileLength.Next(50);			// add 50 using method in JumpCounter
  ...
  fileLength.Reset();			// uses method in DisplayableNumber

In general an inheritance diagram may form a tree structure, each node in the tree being a class that inherits from its parent (base class) and also serves as a base class for its descendents. The exercises below, when completed, will build an inheritance graph that look like the following:



The experience of designing a new class by inheritance should be that the introduction of a new variation or extension of an existing class is easier when compared to the job of reimplementing the class entirely from scratch.

It will become clearer as more is learned that designing the "right" inheritance structure is both important and difficult. Much of the effort in object-oriented programming is invested in the search for a "good" inheritance structure.

Exercises

  1. The class DownCounter maintains an integer counter whose non-negative value, given initially by a constructor argument, is decremented by one on each call to its Next operation until the value reaches zero. Once reaching zero, subsequent calls on Next will have no effect, the value remains at zero. The value of a DownCounter object can be displayed in a TextBox. Using an inheritance diagram, show how the DownCounter class is defined by inheritance from DisplayableNumber.

  2. The class UpDownCounter maintains an integer counter whose non-negative value, given initially by a constructor argument, is incremented by one on each call to its Next operation. The UpDownCounter class also has a Previous method that decrements the counter's value by one. The counter's value cannot be decremented lower than its initial value. The value of a UpDownCounter object can be displayed in a TextBox. Using an inheritance diagram, show how the UpDownCounter class is defined by inheritance from Counter.

  3. The class BiCycler is to be defined that maintains an integer counter that is incremented modulo the base given by the constructor. The BiCycler class also has a Previous method that decrements the counter's value by one. When the internal value is zero, the Previous operation sets the internal value to the largest possible value (i.e., base-1). Using an inheritance diagram, show how the BiCycler class is defined by inheritance from Cycler.

  4. The class SwitchCounter maintains an integer counter that is incremented or decremented by the Next method depending on the current "direction" of the SwitchCounter. Initially the SwitchCounter is in the "up" direction. When in the "up" direction the value is incremented by 1 when Next is called. When in the "down" direction the value is decremented by 1 when Next is called. The Switch method changes the direction to the opposite of its current setting. Using an inheritance diagram, show how the SwitchCounter class is defined by inheritance from DisplayableNumber.

  5. The class BatchCounter maintains an integer counter that is incremented by 1 only when the Next operation has been called "n" times. The value of "n" is given as a constructor argument. This class counts "batches" of Next operations. It may be used, for example, to increment a one minute counter only after its Next method has been called sixty times by a one second Clock. Using an inheritance diagram, show how the BatchCounter class is defined by inheritance from DisplayableNumber.