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 valueNotice 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.