6.9 Refactoring of Base Classes



Developing a derived class may lead to a redesign or a refactoring of the base class which is done so that the derived class can be accommodated without sacrificing the integrity of the base class itself. In general, the interface between the base class and its derived classes is as important, and requires as much effort to develop and maintain, as the interface between an object and the object's clients. The example given in this section illustrates a situation in which base class redesign is needed to accommodate a new derived class.

Refactoring a base class is often required to preserve the base class's information-hiding properties of the base class. Information hiding is a software engineering principle stating that knowledge of design decisions (the information) should have the narrowest possible dissemination (the hiding). This principle helps to insure the independence of different parts of the system. Furthermore, information hiding promotes easier maintenance and adaptation of software, because a change in a design decision does not have a widespread impact if information about that design decision has been circumscribed. However, if many, or seemingly unrelated, parts of the system are conditioned on knowledge of that design decision, then a change in the design decision may render invalid these other parts of the system.

As an example, the DisplayableNumber class must be refactored to accomodate an OctalNumber class because the Show method is inappropriate for the new class. An OctalNumber object is just like a Number object, except that the OctalNumber object's value is displayed using octal-number notation and not as a decimal value. For example, an OctalNumber object with the decimal value 23 would be displayed in its octal representation as 27. While most of the methods in the Number and DisplayableNumber classes are exactly those needed to implement an OctalNumber, the Show method is inappropriate because it displays values in a decimal notation.

Simply overriding the Show method in the OctalNumber class leads to a loss of information hiding. A first approach to modifying the Show method is to provide an overriding Show method: the inherited Show method must be replaced (overriden) by a Show method that displays the value in octal notation. However, overriding the Show method leads to a dilemma regarding the sharing of data between the base and derived classes. The Show method uses the TextBox variable to display the DisplayableNumber's current value. To override the Show method in a derived class, the TextBox variable must be placed in the protected region of the base class. This change, while workable, significantly enlarges the scope (i.e., weakens the protection) of the TextBox variable, as the base class no longer completely encapsulates the existence of the TextBox variable. Therefore, both the base class and its derived classes are dependent on the following design decisions:

  • The DisplayableNumber's value is displayed in a TextBox,
  • The TextBox is accessed via a pointer,
  • The name of the pointer is textBox, and
  • The TextBox has a SetText method.

If any of these design decisions change, then both the base class and all of its derived subclasses are vulnerable to change. Notice how serious a problem this is: a derived class (OctalNumber) may need to be changed because of a change in a class (TextBox).

The cause of the problem is that the Show method performs two related, but separable, actions, which are formatting the character string to be displayed, and displaying the string in the TextBox. The design dilemma is easily resolved by recognizing that the OctalNumber class only needs to override the first action, and only the second action requires knowledge of the TextBox class and the TextBox variable. A new virtual method, char* Format(), is added to the base class to perform the first action. The revised Show() method will use the Format method.

Separating the two actions of the Show method leads to the following revised definition and implementation of the DisplayableNumber class. Notice that the Format method is a virtual method, meaning that an overriding of this method by a derived class will be used in preference for the default (decimal formatting) given in the DisplayableNumber class.


Refactoring the DisplayableNumber Class
class DisplayableNumber 
{
    private:
      TextBox* textBox;
    protected:
      int value;
      virtual char* Format();           // produce string to display
    public:
      DisplayableNumber(int initValue = 0);   
      void ShowIn(TextBox& p);
      void Show();
      void Reset();
      ~DisplayableNumber();
};
char* DisplayableNumber::Format() 
{  char* asString = new char[10];
   ostrstream format(asString);
   format << value;                      // use decimal formatting
   return asString;
}
void DisplayableNumber::Show() 
{
   if (textBox) textBox->SetText(Format());
}

Note, however, this refactoring of the base class is incomplete because the Reset method has not been accounted for. When Reset is called, it is expected to produce a new value for the DisplayableNumber by parsing the string obtained from the TextBox. However, this parsing is done assuming that the string represents a decimal value. Once again, this is inappropriate for the OctalNumber class because the string "77" parsed as a decimal value is clearly different from this same string parsed as an octal value.

In the Reset method there are found again two separable actions: obtaining from the user the string that represents the new value, and parsing this string to determine the new value Following the principle of information hiding, the first of these actions will remain the private concern of the base class. The second action will be established as a virtual method in the protected section of the base class. The additional changes needed in the DisplayableNumber are shown in the figure below.


Additional Refactoring of the Base Class
class DisplayableNumber 
{
    private:
      TextBox* textBox;
    protected:
      int value;
      virtual char* Format();           // produce string to display
     virtual int Parse(char* input);   // convert user input to value   public:
      // ... as before
};
int DisplayableNumber::Parse(char* input) 
{  int decimalValue;
   istrstream format(input);
   format >> decimalValue;                      // use decimal formatting
   return decimalValue;
}
void DisplayableNumber::Reset() 
{
   if (textBox) value = Parse(textBox->GetText());
}

Using this approach, implementing the OctalNumber class becomes straightforward, the code for which is shown below. Since OctalNumber inherits from Number, the only method that the OctalNumber must implement is the Format method. The Format method is declared in the protected section of the OctalNumber class so that it does not become a part of the public interface of this class.


Defining the Octal Number Class
class OctalNumber : public Number
{ protected:
     char* Format();
     int   Parse(char* input)
 public:
    OctalNumber(int init);
    ~OctalNumber();  
};
// in the implementation file
OctalNumber::OctalNumber (int init) : Number(init)
{
}
char* OctalNumber::Format()
{ char* asString = new char[10];
  ostrstream format(asString);
  format << oct << value; // format value as octal
  return asString;
}
int OctalNumber::Parse(char* input)
{ int octalValue;
  istream is(input);
  is.flags(ios::oct);
  is >> octalValue;
  return octalValue;
}
OctalNumber::~OctalNumber() {}

  1. Implement and test the refactored DisplayableNumber and OctalNumber classes described in this section.

  2. Implement and test a class HexNumber that is like a Number except that it displays itself in hexadecimal notation. Develop this class as a subclass of Number using the refactored DisplayableNumber class.

  3. Implement and test a PageNumber class that is like a Number except that it displays itself with the string "Page " preceeding the decimal value of the number. Develop this class as a subclass of Number using the refactored DisplayableNumber class.

  4. Implement and test a DollarNumber class that is like a Number except that it displays itself with the character $ preceeding the decimal value of the counter. Develop this class as a subclass of Number using the refactored DisplayableNumber class.

 




©1998 Prentice-Hall, Inc.
A Simon & Schuster Company
Upper Saddle River, New Jersey 07458

Legal Statement