|< Free Open Study >|
5.17 The Attempt to NOP a Base Class Method in Its Derived Class(es)
Another interesting problem that arises in the construction of inheritance hierarchies occurs when the designer attempts to NOP a base method in the derived class, that is, define an empty method in the derived class. While teaching a C++ course at a telecommunications company, I introduced the following heuristic.
An attendee of the course objected, explaining that their developers did this all of the time. Rather than explaining the actual problem involving classes in their domain combined with a C++ binding for OSF Motif, the objecting person drew a real-world analogy. She stated that they had a situation in which there is an existing class called Dog. The behaviors that all Dogs know how to carry out is bark, chase_cats, and wag_tail. Later in development they discovered a special type of Dog (a derived class) called DogNoWag. This new class was exactly like a Dog except it didn't know how to wag its tail. They claimed that a good solution was to have DogNoWag inherit from Dog and override the wag_tail method with an empty C++ method (i.e., a NOP) (see Figure 5.39).
A number of other students in the class began to shout out supporting statements such as, "What about dogs with paralyzed tails?" "What about dogs with cut-off tails?" "What about dogs with broken tails?" and "What about stupid dogs who haven't learned to wag their tail?" I have encountered this phenomenon with increasing frequency. These questions imply a completely different design problem than the original, although the originators assume they are bolstering the same argument. There are at least two separate arguments being carried out here. I will create three separate arguments from these questions, in order to introduce a third concept.
The first argument is the one raised by the original problem. What is wrong with the design presented in Figure 5.39? The main objection is that the design does not capture a logical relationship. The design implies the following statements:
Obviously, the rules of classic logic are not being obeyed. Either all dogs do not know how to wag their tails or DogNoWag is not a special type of dog. Why should we preserve classic logic? My main argument is that a designer can now use inheritance without restriction. Anything could be considered a specialization of anything else by NOPing all of its base class public interface and adding a new derived public interface.
I have seen this NOPing problem entering many designs since the issue was first raised in this course. I believe the problem is psychological: It has always occurred in designs where a derived class is already present and a base class is being added. For whatever reason, designers tend to consider any new class added to a design as being a derived class of the existing classes. When the new base class is added, it is forced to inherit from something that should be its derived class. The result is to eliminate some of the functionality of the derived (acting as base) class via NOP methods. The correct design is found by flipping the hierarchy upside down, making the base class the derived class and the derived class the base class (see Figure 5.40).
However, in some cases, the DogNoWag class has some message/method that the Dog class does not have. In these cases, neither class is a derived class of the other; they simply have something in common. This common information is captured in an abstract class (e.g., AllDogs), and both classes inherit from the abstract class (see Figure 5.41). This latter solution always eliminates the problem of NOP functions.
A recently published introductory object-oriented text contained an example that argued against this heuristic. The domain was that of the animal kingdom, with a focus on platypuses. The author argued that the real world is more complex than that of simple inheritance. We like to categorize things with exceptions, making statements such as
Such a statement maps to the design in Figure 5.42 of mammals and platypuses.
This design is clearly equivalent with the dogs and their tails design: It violates classic logic. Either platypuses are not mammals, or not all mammals give live birth. But what if this is the way human beings like to think of the relationship between mammals and platypuses? Shouldn't we be allowed to model our domains in the way we like to think about them? I will argue against this type of justification. I do not think we can correctly think of a platypus as a special type of mammal unless we are willing to give up the notion that all mammals give live birth. What we really need to state is that mammals and platypuses have a lot in common. That common information needs to be stored in a new base class from which both Platypus and Mammal inherit (see Figure 5.43). This is the same solution we used for the dog example.
What about the stupid dogs, dogs with paralyzed tails, broken tails, cut-off tails? These are not special classes of dogs: They are dogs with bad state. They still have a wag_tail method, which checks the state of the dog, finds that the state is insufficient to manifest behavior, and terminates. This method is not a NOP梚t has behavior. If these states were modeled as classes, we would get the same problem we had with the Stack, EmptyStack, and NonEmptyStack classes, namely, toggling types. Stupid dogs will learn to wag their tails and change their type from StupidDog to Dog. Later they might bump their heads into walls and be stupid again, necessitating a change in type (see Figure 5.44). Likewise, paralyzed and broken tails heal, cut-off tails are stitched back on, etc. You would not want to create classes like CarWithDeadBattery, CarWithStolenBattery, CarWithBadStarter, CarWithCrackedBlock, or CarWithWrongEngine. These are simply examples of cars with bad state.
|< Free Open Study >|