6.6 Hiding Inherited Methods

The Problem

It is occassionally, though rarely, necessary to prevent a base class method from appearing in the public interface of a derived class. This must happen, however, if the inherited method is inappropriate to objects of the derived class and when the action taken by the inherited method is in conflict with the specialization introduced by the derived class. The two examples explored below illustrate this situation, and three ways of hiding inherited methods are discussed.

Hiding inherited methods is a controversial technique. Some argue that it is a violation of the "is-a" principle that underlies inheritance. The standard for judging the appropriateness of inheritance is whether the derived class "is a" kind of the base class. Strictly interpreted, a hidden method is an exception to the "is-a" rule: the derived class is a kind of the base class, but it cannot perform this action defined in the base class. This argument is clearly correct if the hiding is too extensive because, with sufficient hiding two very dissimilar abstractions can be related through inheritance. For example, if the class Dog defines the properties "bark," "fetch," and "eat food" then a class Cat can be derived from Dog by hiding "bark" and "fetch" and adding "purr" and "sleep." In this case, the inheritance does not reflect a meaningful relationship in the underlying abstractions. Others argue, however, that hiding is acceptable if used judiciously in limited circumstances. In their view, some specializations are, in fact, defined by a limitation on a more generalized notion. For example, a Window class would define many operations (show, hide, move, resize, change color, and iconify, among others) all of which save the resize operation are needed to define a FixedSizedWindow abstraction. In this case, hiding the single operation seems acceptable.

The first example of inheriting an inappropriate method involves the Rectangle class shown below. The Rectangle defines a number of methods that are useful in defining a Square class. However, if class Square is derived from the Rectangle class, it will inherit the SetShape method, which is not an appropriate method for the Square class because it would allow a Square object (by definition, with equal height and width) to be set to an arbitrary Shape. There is no guarantee that the argument passed to the SetShape method would object to the restrictions of the derived Square class.


Inheriting an Inappropriate Method
class Rectangle
{ //...
  public:
    Rectangle(Shape shape);
    void SetLocation(Location loc);
    void SetShape(Shape shape);
    void Draw();
    void Clear();
    void Rotate();
};

class Square : public Rectangle
{ 
  // inherits SetShape method
};

The second example of inheriting an inappropriate method is in the DisplayableNumber hierarchy. Suppose that a new ProgramControlledCounter class is to be developed whose objects behave just like objects of the Number class, except that the value of ProgramControlledCounter objects can only be changed by the program and not by the user. Because of the conceptual and implementation similarity between the ProgramControlledCounter class and the Number class, the ProgramControlledCounter class should be derived from the Number class. However, the Number class inherits from the DisplayableNumber base class a Reset method that allows the user to change the value of the Number object. To define a safe ProgramControlledCounter class it is necessary to remove the Reset method from its public interface. The relevant portions of the inheritance structure are given below.



ProgramControlledCounter Class
class DisplayableNumber
{
  public:
     void Reset(); // allow user to change value
};

class Number : public DisplayableNumber
{
   //...
};

class ProgramControlledCounter : public Number
{
   // inherits the Reset method 
};


Solutions

There are three means of dealing with an inappropriate inherited method: the developer can define a harmless overriding method, use private/protected inheritance, or revise the class hierarchy. Each of these approaches has its own advantges and drawbacks that will be seen by applying them to the examples given above.

Solution One: Overriding the Method

A simple way to deal with an inappropriate method is to retain the method in the public interface but remove its unwanted effect by overriding it and replacing it with a derived class method that either does nothing or takes a benign action. The relevant parts of the code for the Square class using this approach is shown below. This overriding SetShape method guarantees that if the SetShape method is applied to a Square object, the dimensions of the Square are unaffected. In effect, the invocation is silently ignored.


Overriding the Inappropriate Method in the Square Class
class Square : public Rectangle
{
  public:
    Square(int side);           //constructor sets the Shape
 void SetShape(Shape shape); //override inherited method};
// in the implementation

Square::Square(int side) : Rectangle(Shape(side,side)) {}
void Square::SetShape(Shape shape) {} // do nothing

Alternatively, the SetShape method could be redefined in the Square class to take the dimensions of the Shape object passed as the argument for the new size of the Square. The code for this alternative is

    void Square::SetShape(Shape shape)
     { Rectangle::SetShape(Shape(shape.Width(),shape.Width()));
     }

where only the width of the Shape parameter is used.

The drawback of the do-nothing and benign overriding methods is that they potentially convey misleading information about the class to programmers using the class. It is not unreasonable that a programmer would expect a method to have some effect, especially when the method involved (like SetShape) is known to have effects on objects of all other classes. Another drawback of this approach is finding a reasonable interpretation in the face of type casting. If an object of the Square class is type cast to a Rectangle, the SetShape method applied to the type cast object will be the Rectangle class SetShape method. Is this reasonable? From one point of view it is, because a type cast was performed to view the Square as a Rectangle and to treat it as such. From another point of view, though, it is unreasonable, because the object is actually a Square and its state should not be altered to a form that is not valid. This difficulty can be overcome by changing the base class to make the SetShape method virtual. However, this involves changing the base class and does not resolve the basic issue of the inappropriate method.

 

Solution Two: Protected/Private Inheritance

The second solution uses protected/private inheritance. Thus far, derived classes always used the keyword public in front of the base class name, as in

    class Square : public Rectangle {...}

This use of the public keyword implies that all public methods in the base class become public methods in the derived class. Changing the public keyword to protected (private) means that the public methods of the base class become protected (private) methods in the derived class. By using protected or private inheritance, inappropriate base class methods can be excluded from the public interface of the derived class. The Square class is defined below using private inheritance.


Using Private Inheritance in the Square Class
class Square : private Rectangle
{
  // ...
};

An obvious disadvantage of using private/protected inheritance is that the base class methods that are appropriate for the derived class are also hidden along with the inappropriate methods. In the case of the Square class, this seems disproportionate because only one of the inherited methods is unwanted, while all of the others are useful methods in the public interface of the Square class.

It is possible, however, to name those methods hidden by private/protected inheritance that should be visible in the public interface of the derived class. The syntax required to reveal those methods hidden by private/protected inheritance is shown in the figure below. In this code the Shape class inherits privately from the Rectangle class, thereby hiding all inherited methods. However, the public interface of the Square class declares the fully qualified names of those inherited methods that should be revealed in the public interface of the Square class. Notice that only the fully qualified name of the method is given; no return type or argument list is needed.


Selectively Revealing Hidden Inherited Methods
class Rectangle
{ //...
  public:
    Rectangle(Shape shape);
    void SetLocation(Location loc);
    void SetShape(Shape shape);
    void Draw();
    void Clear();
    void Rotate();
};

class Square : private Rectangle
{ 
   public:
          Square(int side);
     void SetSide(int side);
          Rectangle::SetLocation;
          Rectangle::Draw;
          Rectangle::Clear;
          Rectangle::Rotate;
         ~Square();
};

// implementation file
Square::Square (int side) : Rectangle(Shape(side,side)) {}

void Square::SetSide(int side)
{ SetShape(Shape(side,side));
}

Square::~Square() {}  //no actions necessary

The Square class defines five public methods in addition to the constructor and destructor, only one of which, the SetSide method, is newly defined in the Square class. The other four public methods of the Square class are obtained by private inheritance and then explicitly named in the public interface of the Square class. These four methods are: SetLocation, Draw, Clear, and Rotate. Notice that the implementation of the Square class needs only to provide the code for the methods added in this class (the constructor, the destructor, and the SetSide method).

 

Solution Three: Revise the Inheritance Hierarchy

In some cases changes in the class hierarchy can eliminate cases where an inappropriate method is inherited. In both of the examples, Square and ProgramControlledCounter, the inappropriate method was one that allowed the object to be changed in a manner that conflicted with the meaning of the derived class. The nature of this change can be used to divide the class hierarchy into two parts - those classes for which the change is appropriate and those classes for which the change is not appropriate. To reflect this division it may be necessary to introduce a new base class that contains the methods needed by both of the sets of classes.

The problem with the Square and Rectangle classes can be resolved by factoring the common methods into a new base class, Quadrilateral, and establishing two derived classes, Rectangle and Square. Each of the derived classes introduces methods that allow their dimensions to be altered as appropriate for their class. The Quadrilateral class and the revised class hierarchy is shown in Code Sample 6/13.


Altering the Inheritance Hierarchy
class Quadrilateral
{ //...
  public:
    Quadrelateral(Shape shape);
    void SetLocation(Location loc);
    void Draw();
    void Clear();
    void Rotate();
};

class Rectangle : public Quadrilateral
{
  public:
    Rectangle(Shape shape);
    void SetShape(Shape shape);
   ~Rectangle();
};

class Square : public Quadrilateral
{
  public:
    Square(int side);
    void SetSide(int side);
    ~Square();
};

In this revised class hierarchy, no inappropriate methods are inherited. The Rectangle class inherits all of the methods of the Quadrilateral class and adds a method that allows both dimensions of a Rectangle object to be changed. Similarly, the Square class inherits all of the methods of the Quadrilateral class and adds a method that allows only a change in dimensions that will affect both dimensions simultaneously.

 

  1. Define the outline of a revised class hierarchy that deals with the inappropriate method inherited by the ProgramControlledCounter class.

  2. Show how private/protected inheritance could be used to resolve the problem of the inappropriate method inherited by the ProgramControlledCounter class.

  3. Under what circumstances would you use private inheritance rather than protected inheritance? Why?

 




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

Legal Statement

 

ÿ