![]() |
|||||||||||||||
| 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.
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.
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.
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.
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.
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.
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.
|
|||||||||||||||
|
|||||||||||||||
ÿ