The Ellipse-Circle Dilemma

Source: comp.object
Date: 30-Jun-97

Related Sites


------------------------------

o-< Problem: Is a circle a kind-of an ellipse? Maybe an ellipse is an extension of circle?

This is an instance of two more general problems:

  1. Should the OO model (Ellipse class and Circle class) of an application match the Real World model (mathematical concepts of ellipse and circle)?
  2. Does similarity between classes always implies inheritance?

---------------

o-< Russ McClelland wrote:

A circle is an ellipse, ask any mathematician, the real question is can a circle be modeled in software exactly as it's defined in the real world. Yes, just because an ellipse has two foci, and a circle apparently has one, doesn't mean that we need to create an independent circle class, they both have the same attributes. It is an optimization to remove the second focus for circles.


---------------

o-< Terry Richards then noted that:

Nobody says a line is an ellipse, nobody says a point is an ellipse, so why say that a circle is an ellipse?


---------------

o-< Roger T. was quick to reply:

Because a circle, in every way, meets the definition of an ellipse. That definition is; x^2/a^2 + y^2/b^2 = 1


---------------

o-< Robert C. Martin then pointed out the difference between mathematics and OOD:

No! That is the geometric definition. The OO definition of an ellipse is somewhat different.

class Ellipse
{
public:
    Ellipse(const Point& f1, const Point& f2);
    double Area();
    double Circumference();
    Line MajorAxis();
    Line MinorAxis();
    // and maybe some accessors and mutators.
private:
    Point f1;
    Point f2;
};
This is just one possibility for the definition of an Ellipse in an OOPL. There are an infinite variety of others depending upon the application in which it is to be used.

Now, the definition I used above cannot be collapsed into a special case as you have done below.

Because the Circle ought not to have two foci. The circle ought not to have functions that retrieve the major and minor axes.

We want the circle class to use algorithms that are tuned to operate on circles. We don't want to use algorithms that are tuned for ellipses and just happen to produce the right answer for circles after a lot of extra work.

If we were to use the notion of special cases then the following logic would apply:
A circle is a state of an Ellipse. An Ellipse is really a state of a Conic Section. A conic section is really a state of a polynomial. A polynomial is really a state of a generic nth order equation.

So perhaps we should have a GenericNthOrderEquation class and then circles, ellipses, paraboli ,hyperboli, lemnicati, etc, etc, would all be states of this class.

But I don't think anybody would want to use such a class.


---------------

o-< Lex Spoon questioned the importance of the ellipse-circle dilemma to every-day practice:

Reading through some of the complicated descriptions in this thread, I can't help but think about some poor programmer who "just" wants to write a program that draws a Circle around a smiley face. How much grief should we put him through in the name of purity?


---------------

o-< Robert C. Martin explained:

Don't underestimate this issue. It is not an issue of purity, it is an issue of software engineering.

The whole circle/ellipse discussion is really an instance of a much broader concept. That is Programming by Contract.

Every method of every class has a set of preconditions and postconditions. The preconditions must be true before the function is called, otherwise the results of the function will be undefined. The function guarantees that the postconditions will be true once it returns.

Now, given function u that invokes function f in some class. Function u ensures the preconditions for f are true and then expects the post-conditions of f to be true once f returns.

However, if u invokes f on a base class b, what can we say about the pre and post conditions of f in d a derivative of b? Bertrand Meyer worked all this out in the mid 80s and wrote it down in a wonderful book called Object Oriented Software Construction.

Meyer said that the preconditions of d::f can be no stronger than the preconditions of b::f. That is, derived classes cannot their users do to more than users of b::f are already doing. Moreover, d::f can accept fewer preconditions. This does no harm since users of b::f will still be able to call d::f.

Also, d::f must conform to all of the postconditions of b::f. This ensures that functions like u can call d::f polymorphically and still get everything they need. d::f has the ability to add more postconditions if necessary, but that is just gravy as far as u is concerned.

Now, consider the Circle/Ellipse problem. One of the functions of Ellipse is StretchX(x). This function stretches the ellipse by x units in the X direction. The post conditions for this function might be:
  new horizontal length is x units longer.
  new vertical height is unchanged.

These are reasonable postconditions for this function, and u can depend upon them.

However, if we derive Circle from Ellipse and then modify StretchX such that it also increases the vertical height of the circle in order to keep it circular, then we have violated the postconditions of Ellipse::StretchX. This will confuse any users (u) that call StretchX on what they think are Ellipses.

Of course these issues are important for all classes. Every method of every class has a set of preconditions and postconditions. And it is important to make sure that the methods of derived classes expect no more than the bases, and deliver no less than the bases.

This is just another way of stating the Liskov Substitution Principle (LSP)


------------------------------

o-< More Info:

Alistair Cockburn, Constructive Deconstruction of Subtyping

From the comp.lang.c++ FAQ


------------------------------