15

Having Circle extend Ellipse breaks the Liskov Substition Principle, because it modifies a postcondition: namely, you can set X and Y independently to draw an ellipse, but X must always equal Y for circles.

But isn't the problem here caused by having Circle be the subtype of an Ellipse? Couldn't we reverse the relationship?

So, Circle is the supertype - it has a single method setRadius.

Then, Ellipse extends Circle by adding setX and setY. Calling setRadius on Ellipse would set both X and Y - meaning the postcondition on setRadius is maintained, but you can now set X and Y independently through an extended interface.

10
  • 1
    Did you look into Wikipedia first (en.wikipedia.org/wiki/Circle-ellipse_problem)?
    – Doc Brown
    Commented Apr 4, 2016 at 5:42
  • 1
    yes - i even link it in my question...
    – HorusKol
    Commented Apr 4, 2016 at 5:53
  • 6
    And this exact point is covered in that article, so I'm unclear as to what you're asking? Commented Apr 4, 2016 at 5:58
  • 6
    "Some authors have suggested reversing the relationship between circle and ellipse, on the grounds that an ellipse is a circle with additional capabilities. Unfortunately, ellipses fail to satisfy many of the invariants of circles; if Circle has a method radius, Ellipse will now have to provide it as well." Commented Apr 4, 2016 at 7:38
  • 4
    What I found to be the clearest explanation about why this problem has bad premises lay at the very bottom of the wikipedia article : en.wikipedia.org/wiki/…. Depending of the situation, there is several clean designs, but it depends of what you need from these two classes to do, not to be.
    – Diane M
    Commented Jun 17, 2016 at 9:03

6 Answers 6

42

But isn't the problem here caused by having Circle be the subtype of an Ellipse? Couldn't we reverse the relationship?

The problem with this (and the square/rectangle problem) is falsely assuming a relationship in one domain (geometry) holds in another (behaviour)

A circle and ellipse are related if you are viewing them through the prism of geometrical theory. But that is not the only domain you can look at.

Object orientated design deals with behaviour.

The defining characteristic of an object is the behaviour the object is responsible for. And in the domain of behaviour, a circle and ellipse have such different behaviour that it is probably better not to think of them as related at all. In this domain, an ellipse and a circle have no significant relationship.

The lesson here is to choose the domain that makes the most sense for OOD, not to try and shoehorn in a relationship simply because it exists in a different domain.

The most common real-world example of this mistake is to assume objects are related (or even the same class) because they have similar data even if their behaviour is very different. This is a common problem when you start constructing objects "data first" by defining where the data goes. You can end up with a class that are related via data that have completely different behaviour. For example, both the payslip and the employee objects might have a "gross salary" attribute, but an employee is not a type of payslip and a payslip is not a type of employee.

5
  • Separating the concerns of the (application) domain vs. the behavioral and responsibility capabilities of OOD is a very important point. For example, in an drawing application, you perhaps ought to be able to morph a circle into a square, but this isn't easily modeled using classes/objects in most languages (as objects usually can't change class). So, the application domain doesn't always map to nicely to a given OOP language's inheritance hierarchy and we shouldn't try to force it to; in many cases, composition is better.
    – Erik Eidt
    Commented Apr 4, 2016 at 16:10
  • 3
    This answer is by far the best thing I've seen about the whole issue, and how the potential for design mistakes can arise in more general cases. Thanks
    – HorusKol
    Commented Apr 4, 2016 at 23:02
  • 1
    @ErikEidt The problem of an object changing behaviour can be solved in OOD through decomposition. For example if a morphable shape changes into a circle, you don't have to change the class. Instead the class takes a current geometric behaviour object that you can swap out for another behaviour when you morph. This other class contains the rules of the geometric shape currently being modelled, and the morphable shape class defers to this class for geometric behaviour. If the object morphs into a different class you change the behaviour class to something else. Commented Apr 5, 2016 at 14:07
  • 2
    @Cormac, right! Generically I'd call that a form of composition, as I mentioned, though you could identify, more specifically, a strategy pattern or something. In essence, you have an identity that doesn't morph, and other things that can then be changed. All in all a good highlighting of the difference between application domain concepts, and the particulars of a given language's OOP, and the need to map between them (i.e. architecture, design, and programming).
    – Erik Eidt
    Commented Apr 5, 2016 at 15:34
  • 1
    But a job can be a paycheck.
    – user251748
    Commented Mar 8, 2018 at 16:06
9

Cormac has a really great answer, but I just want to elaborate a bit on the reason for the confusion in the first place.

Inheritance in OO is often taught using real-world metaphors, like "apples and oranges are both sub-classes of fruit". Unfortunately this leads to the mistaken belief that types in OO should be modeled according to some taxonomical hierarchies existing independent of the program.

But in software design, types should be modeled according to the requirements of the application. Classifications in other domains are usually irrelevant. In an actual application with "Apple" and "Orange" objects - say an inventory management system for a supermarket - they will probably not be distinct classes at all, and categories like "Fruit" will be attributes rather than supertypes.

The circle-ellipse problem is a red herring. In geometry a circle is a specialization of an ellipse, but the classes in your example are not geometrical figures. Crucially, geometrical figures are not mutable. They can be transformed, though, but then a circle can be transformed into an ellipsis. So a model where circles can change radius but not change to an ellipsis does not correspond to geometry. Such a model might make sense in a particular application (say a drawing tool) but geometrical classification is irrelevant for how you design the class hierarchy.

So should Circle be a subclass of Ellipse or vice versa? It totally depends on the requirements of the particular application which uses these objects. A drawing application could have different choices in how to treat circles and ellipses:

  1. Treat circles and ellipses as distinct types of shapes with different UI (e.g. two resize-handles on an ellipsis, one handle on a circle). This means you can have an ellipse which is geometrically a circle but not a Circle from the perspective of the application.

  2. Treat all ellipses including circles the same, but have an option to "lock" x and y to the same value.

  3. Ellipses are just circles where a scaling transformation have been applies.

Each possible design will lead to different object model -

In the 1st case, Circle and Ellipses will be sibling classes

In the 2nd one, there will not be a distinct Circle class at all

In the 3rd one, there will not be a distinct Ellipse class. So the so-called circle-ellipse problem does not enter the picture in any of these.

So to answer the question as posed: Should circle extend ellipse? The answer is: It depends on what you want to do with it. But probably not.

2
  • 1
    A very good answer !
    – Utsav T
    Commented Dec 12, 2018 at 11:22
  • And now we run into another classification problem on StackOverflow... if this gets enough upvotes, this answer will rise above Cormac's answer, and appear to be the main answer, rather than an elaboration on a previous answer... :)
    – drkvogel
    Commented Jan 21, 2022 at 0:44
8

Circles are a special case of ellipses, namely that both axes of the ellipsis are the same. It is fundamentally false in the problem domain (geometry) to state that ellipses might be a kind of circle. Using this flawed model would violate many guarantees of a circle, for example “all points on the circle have the same distance to the center”. That too would be a Liskov Substitution Principle violation. How would an ellipse have a single radius? (Not setRadius() but more importantly getRadius())

While the modelling of circles as a subtype of ellipses is not fundamentally wrong, it is the introduction of mutability that breaks this model. Without the setX() and setY() methods, there is no LSP violation. If there is a need to have an object with different dimensions, creating a new instance is a better solution:

class Ellipse {
  final double x;
  final double y;
  ...
  Ellipse withX(double newX) {
    return new Ellipse(x: newX, y: y);
  }
}
2
  • 1
    okay - so, if there were some common interface between Ellipse and Circle (such as getArea) that would be abstracted to a type Shape - could Ellipse and Circle separately subtype from Shape and satisfy LSP?
    – HorusKol
    Commented Apr 4, 2016 at 6:58
  • 1
    @HorusKol Yes. Two classes inheriting an interface that they both really do implement correctly is completely fine.
    – Ixrec
    Commented Apr 4, 2016 at 7:25
6

It's a mistake from the start to insist of having an "Ellipse" and a "Circle" class where one is a subclass of the other. You have two realistic choices: One is to have separate classes. They may have a common superclass, for things like color, whether the object is filled, line width for drawing etc.

The other is to have one class named "Ellipse" only. If you have that class, it's easy enough to use it to represent circles (there may be traps depending on implementation details; an Ellipse will have some angle and the calculation of that angle must not run into trouble for a circle-shaped ellipse). You could even have specialised methods for circular ellipses, but these "circular ellipses" would still be full "Ellipse" objects.

1
  • There could be an IsCircle method which would check to see if a particular object of class Ellipse in fact has both axes the same. You pointed out the angle issue also. Circles cannot be 'rotated'.
    – user251748
    Commented Mar 8, 2018 at 16:17
3

Following the LSP points, one 'proper' solution to this problem is as @HorusKol and @Ixrec came upon - deriving both types from Shape. But it depends on the model you are working from, so you should always go back to that.

What I was taught is:

If the sub-type cannot perform the same behaviour as the super-type, the relationship doesn't hold in the IS-A premise - it should be altered.

  • A sub-type is a SUPERSET of the super-type.
  • A super-type is a SUBSET of the sub-type.

In english:

  • A derived-type is a SUPERSET of the base-type.
  • A base-type is a SUBSET of the derived-type.

(Example:

  • A car with a bad-boy exhaust is still a car (according to some).
  • A car without an engine, wheels, steering rack, drivetrain, and only the shell left, is not a 'car', it's just a shell.)

That's how classification works (ie. in the animal world), and in principal, in OO.

Using this as the definition of inheritance and polymorphism (which always get written together), if this principle is broken, you should be trying to re-think the types you are trying to model.

As mentioned by @HorusKul and @Ixrec, in maths you have clearly defined types. But in maths, a circle is an ellipse because it is a SUBSET of ellipse. But in OOP this is not how inheritance works. A class should only inherit if it is a SUPERSET (an extension) of an existing class - meaning, it still IS the base class in all contexts.

Based on that, I think the solution should be slightly reworded.

Have a Shape base type, then RoundedShape (effectively a circle but I've used a different name here DELIBERATELY...)

...then Ellipse.

That way:

  • RoundedShape is a Shape.
  • Ellipse is a RoundedShape.

(This now makes sense to people in language. We have a clearly defined concept of a 'circle' in our minds already, and what we're trying to do here by generalising (aggregation) breaks that concept.)

1
  • Our clearly defined concepts do not always work out in practice.
    – user251748
    Commented Mar 8, 2018 at 16:13
-1

From an OO perspective ellipse does extend circle, it specializes on it by adding some properties. The existing properties of circle still hold in ellipse, it just gets more complex and more specific. I do not see any issues with behavior in this case like Cormac does, shapes have no behavior. The only trouble is that in a liguistic or mathematical sense it does not feel right to say "an ellipse IS A circle". Because the whole point of the exercise that isn't mentioned but is nonetheless implicit, was to classify geometric shapes. That may be a good reason to regard circle and ellipse as peers, not link them by inheritence and accept that they just happen to have some of the same properties and NOT let your twisted OO mind have its way with that observation.

Not the answer you're looking for? Browse other questions tagged or ask your own question.