FAQ 8.09 What can be done about the asymmetric-circle dilemma?

Admit that you can't have it all.

Despite its intuitive appeal, and regardless of how many other member functions are inheritable, deriving Circle from Ellipse causes problems. The problems stem from the inherent incompatibility of the following statements.

  1. Every Ellipse can be stretched asymmetrically.

  2. Circle is substitutable for Ellipse.

  3. A Circle cannot be stretched asymmetrically.

At least one of those statements must be false. The options are exactly the same as with the Ostrich / Bird dilemma. Hmmm. Is there a pattern here? The three options follow.

  • Invalidating statement 1 requires that either Ellipse::setSize(x,y) be removed or given an adaptable specification such as "This may or may not do something." Either way, the change may break existing code that relies on the former promises made by class Ellipse.

  • Invalidating statement 2 prohibits passing a Circle as a kind-of Ellipse.

  • Invalidating statement 3 means that Circle must be able to stretch asymmetrically, which is not mathematically desirable.

The basic problem is that Ellipse is too strong. Ellipse has such powerful member functions that Circle can't be substituted. The options are to weaken Ellipse, strengthen Circle, or admit that a Circle isn't substitutable for an Ellipse.

The situation occurred as a result of an inadequate domain analysis. Either the requirements for class Ellipse were not understood or the consequences of trying to define Circle as substitutable for Ellipse were overlooked. Remember: do not confuse substitutability with specialization. Because a circle is a specialized ellipse (circles are ellipses that have an extra symmetry constraint) does not imply that Circle is substitutable for Ellipse. The substitutability relation has to do with behaviors, not specialization. When evaluating a potential inheritance relationship, the only criterion should be substitutability. The derived class should never surprise code that expects a properly functioning base class.

Note that if Circle::setSize(x,y) is redefined so that it throws an exception, it would break user code unless the specification of Ellipse::setSize(x,y) allows exceptions to be thrown. If Circle::setSize(x,y) is redefined to be a no-op, it would break user code unless the specification of Ellipse::setSize(x,y) says, "This member function might do nothing."

There are other ways to illustrate this basic issue. Integer is not substitutable for RationalNumber if RationalNumber has a divide-self-by-two member function (unless the divide-self-by-two member function has a weak specification, such as "Multiplying the result by two might not give the original number").

This issue is important because it comes up so often in real-life situations. This is not about ellipses or birds or integers. This is about wasted money and failed projects. Unfortunately, the projects that are most vulnerable to improper inheritance are the larger ones with bigger budgets and more to lose.



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