FAQ 8.04 Is an Ostrich a kind-of Bird ?

FAQ 8.04 Is an Ostrich a kind-of Bird?

graphics/new_icon.gif

Not if all birds can fly!

Suppose a system uses a base class Bird with member functions altitude() and fly(), where fly() promises that the bird's altitude() will be greater than zero.

 class Bird { public:   int altitude() const;     // PROMISE: Returns this Bird's current altitude   virtual void fly();     // PROMISE: altitude() will return a value > 0   virtual ~Bird(); }; Bird::~Bird() { } 

Can an Ostrich class inherit from Bird? Unfortunately, ostriches can't fly (that is, the altitude of an ostrich remains at zero when it tries to fly). Despite its intuitive appeal and regardless of how many other member functions are inheritable, deriving Ostrich from Bird causes problems. The problems stem from the inherent incompatibility of the following statements.

  1. The altitude of every Bird will be greater than zero when it flies ("all birds can fly").

  2. Ostrich is a kind-of Bird.

  3. The altitude of an Ostrich remains zero after it flies ("ostriches cannot fly").

At least one of the statements must be false; they cannot be simultaneously satisfied. We examine the impact of invalidating each of the three statements. Note that there is no correct solution; the particular situation dictates whichever is best (or perhaps least bad).

  • Invalidating statement 1 requires changing the behavior of Bird to remove the promise that the bird's altitude will be greater than zero when it flies. Unfortunately, this breaks existing user code that relies on the original behavior. In a small system, it might be feasible to change all the user code that is broken, but in large systems this is usually not a practical alternative.

  • Invalidating statement 2 implies that the Ostrich class cannot inherit from the Bird class even though ostrich is a type of bird in the biological sense. In this case the reason is that Bird really means Bird_That_Can_Fly.

  • Invalidating statement 3 means that the altitude of an Ostrich must be greater than zero after it flies. To make this option more palatable, one can imagine that an Ostrich uses some artificial means to increase its altitude (stairs, a jet-pack, plane tickets, or something like that).

There are two common traps that people typically fall into. The first is to suggest the following as a fourth alternative: create a class such as AnyBird, and inherit both Bird and Ostrich from that new base class. Although there is nothing wrong with this alternative, it is not a fourth alternative. This is merely a repackaging of alternative 2 since it does not try to make Ostrich inherit from Bird and does not try to make Ostrich substitutable for Bird.

The second common trap is to assume that there must be some universal right or best answer for the problem. In reality, it is critical that designers know all three of the tools that can get them out of an improper inheritance situation. In other words, when an improper inheritance situation comes up, make sure to try all three possibilities do not dismiss any of them ahead of time. For example, although the third choice ("make the ostrich fly") seems undesirable in principle, there are times when it may be the most practical. For example, if there is enough code that has already been written based on the promise in the base class, then it will be very expensive to change the base class's promise, ruling out alternative 1. Furthermore, if it is important to be able to pass the derived class into that preexisting code, alternative 2 is ruled out, leaving only alternative 3.

If the problem is caught early enough in the software development life cycle, clearly it is still possible to weaken the base class's promises since there is not yet a lot of existing code that relies on the strong promises made by the base class. So one of the messages here is to think rather than hack. If a problem like this is detected early enough, the cost will be minimal; if it isn't caught until late in the game, the consequences can be pretty grim.

Do not confuse substitutability with subset. Even though the set of ostriches is a subset of the set of birds (every ostrich is in the set of birds), this does not imply that the behavior of Ostrich is substitutable for the behavior of Bird. The substitutability relation has to do with behavior, not subsets. When evaluating a potential inheritance relationship, the important factor is substitutable behavior, not subsets (see FAQ 7.08).



C++ FAQs
C Programming FAQs: Frequently Asked Questions
ISBN: 0201845199
EAN: 2147483647
Year: 2005
Pages: 566
Authors: Steve Summit

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net