123

If a Square is a type of Rectangle, than why can't a Square inherit from a Rectangle? Or why is it a bad design?

I have heard people say:

If you made Square derive from Rectangle, then a Square should be usable anywhere you expect a rectangle

What is the problem here? And why would Square be usable anywhere you expect a rectangle? It would only be usable if we create the Square object, and if we override the SetWidth and SetHeight methods for Square than why would there be any issue?

If you had SetWidth and SetHeight methods on your Rectangle base class and if your Rectangle reference pointed to a Square, then SetWidth and SetHeight don't make sense because setting one would change the other to match it. In this case Square fails the Liskov Substitution Test with Rectangle and the abstraction of having Square inherit from Rectangle is a bad one.

Can someone explain the above arguments? Again, if we over-ride SetWidth and SetHeight methods in Square, wouldn't it resolve this issue?

I have also heard/read:

The real issue is that we are not modeling rectangles, but rather "reshapable rectangles" i.e., rectangles whose width or height can be modified after creation (and we still consider it to be the same object). If we look at the rectangle class in this way, it is clear that a square is not a "reshapable rectangle", because a square cannot be reshaped and still be a square (in general). Mathematically, we don't see the problem because mutability doesn't even make sense in a mathematical context

Here I believe "re-sizeable" is the correct term. Rectangles are "re-sizeable" and so are squares. Am I missing something in the above argument? A square can be re-sized like any rectangle.

yoozer8
  • 693
  • 1
  • 8
  • 20
user793468
  • 1,363
  • 2
  • 10
  • 12
  • 18
    This question seems awfully abstract. There is a gazillion ways to use classes and inheritance, whether or not making some class inherit from some class is a good idea usually depend mostly on how you want to use those classes. Without a practical case I can't see how this question can get a relevant answer. – aaaaaaaaaaaa May 07 '14 at 08:48
  • 4
    Using some common sense it is recalled that square _is_ a rectangle, so if object of your square class cannot be used where rectangle is required it is probably some application design flaw anyway. – Cthulhu May 07 '14 at 09:56
  • 7
    I think the better question is `Why do we even need Square`? Its like having two pens. One blue pen and one red blue, yellow or green pen. The blue pen is redundant - even more so in the case of square as it has no cost benefit. – Gusdor May 07 '14 at 10:25
  • 2
    @eBusiness Its abstractness is what makes it a good learning question. It's important to be able to recognize which uses of subtyping are bad independently of particular use cases. – Doval May 07 '14 at 11:29
  • 5
    @Cthulhu Not really. Subtyping is all about *behavior* and a mutable square doesn't behave like a mutable rectangle. This is why the "is a..." metaphor is bad. – Doval May 07 '14 at 11:31
  • Extending a class should _add_ methods, not _change_ existing methods. For example, you could extend a Rectangle to a [Cuboid](http://en.wikipedia.org/wiki/Cuboid), adding the depth and volume methods, while leaving the width, height, and area methods unchanged. – Joel May 07 '14 at 13:27
  • I think Square might be a dependent type. – Samuel Edwin Ward May 07 '14 at 14:16
  • 2
    @Joel There is no reason you can't change existing methods as long as they implement the correct semantics. Supposing we had different kinds of shapes, they could all have an `area` method whose implementation changes based on the shape. This is fine, as long as the number it returns actually is the area of the shape and not some other number. – Doval May 07 '14 at 14:37
  • @Doval Right, I should have said "change externally-visible behavior". But in your example, the shapes wouldn't inherit from `Rectange` with an `area` method already defined, they would inherit from a `Shape` class, which would not have an immediately callable (non-virtual) `area` method defined. So defining an `area` method would be _adding_ behavior, not _changing_. – Joel May 07 '14 at 15:04
  • @user793468: The main thrust of your argument seems to be that if you can get `Square` code to compile without errors, then it must be correct. That assumption is not justified. `Square` must behave correctly in all situations in which `Rectangle` could possibly be used. This requirement is impossible to fulfil in all cases. – Christian Hayter May 07 '14 at 15:04
  • @Doval Which is why this is a good question: Rectangle and Square (or Circle and Oval) are two of the go-to explanations for explaining why inheritance is nice. Yet they're wrong(ish). Kind of like Fibonacci and recursion, it's a common example that beginners don't understand the nuances of. – Izkata May 07 '14 at 16:08
  • @eBusiness It's actually a good abstraction I'd say. Same example here: http://www.oodesign.com/liskov-s-substitution-principle.html – Mahdi May 07 '14 at 16:18
  • @Izkata Another reason it's kind of wrong-ish is that it's actually an example of how *classifying values of a type* is nice. There's actually no need for inheritance/subtyping in the example - an interface would be a better fit for allowing an unbounded number of shape types. But mainstream OOP languages don't support classification, so you have to fake it with inheritance. – Doval May 07 '14 at 16:34
  • 1
    related (possibly dupes): [Is there a specific name for the "Square inherits from Rectangle" paradox?](http://programmers.stackexchange.com/questions/199331/is-there-a-specific-name-for-the-square-inherits-from-rectangle-paradox) and [How does strengthening of pre conditions and weakening of post conditions violate Liskov Substitution principle?](http://programmers.stackexchange.com/questions/187613/how-does-strengthening-of-pre-conditions-and-weakening-of-post-conditions-violat) – gnat May 08 '14 at 13:34
  • Because a square is just a special and simple rectangle, it would be easier to abstract Rectangle from Square. In Square, you just need to have a `setSides(int)` function to set both the width and height. To abstract Rectangle from Square, all you would have to do is add a `setSides(int, int)` function. If you abstracted Square from Rectangle, you would need to make sure the width and height are the same and have error handling if they are not, while this way you don't need to do that. –  May 09 '14 at 02:04
  • `class shape { getArea(); getBoundingRect(); }` `class rectangle:shape { setWidth(); setHeight(); }` `class square:shape { setSize(); }` – mcrumley May 09 '14 at 15:01

12 Answers12

206

Basically we want things to behave sensibly.

Consider the following problem:

I am given a group of rectangles and I want to increase their area by 10%. So what I do is I set the length of the rectangle to 1.1 times what it was before.

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
  foreach(var rectangle in rectangles)
  {
    rectangle.Length = rectangle.Length * 1.1;
  }
}

Now in this case, all of my rectangles now have their length increased by 10%, which will increase their area by 10%. Unfortunately, someone has actually passed me a mixture of squares and rectangles, and when the length of the rectangle was changed, so was the width.

My unit tests pass because I wrote all my unit tests to use a collection of rectangles. I now have introduced a subtle bug into my application which can go unnoticed for months.

Worse still, Jim from accounting sees my method and writes some other code which uses the fact that if he passes squares into my method, that he gets a very nice 21% increase in size. Jim is happy and nobody is any wiser.

Jim gets promoted for excellent work to a different division. Alfred joins the company as a junior. In his first bug report, Jill from Advertising has reported that passing squares to this method results in a 21% increase and wants the bug fixed. Alfred sees that Squares and Rectangles are used everywhere in the code and realises that breaking the inheritance chain is impossible. He also does not have access to Accounting's source code. So Alfred fixes the bug like this:

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
  foreach(var rectangle in rectangles)
  {
    if (typeof(rectangle) == Rectangle)
    {
      rectangle.Length = rectangle.Length * 1.1;
    }
    if (typeof(rectangle) == Square)
    {
      rectangle.Length = rectangle.Length * 1.04880884817;
    }
  }
}

Alfred is happy with his uber hacking skills and Jill signs off that the bug is fixed.

Next month nobody gets paid because Accounting was dependent on being able to pass squares to the IncreaseRectangleSizeByTenPercent method and getting an increase in area of 21%. The entire company goes into "priority 1 bugfix" mode to track down the source of the issue. They trace the problem to Alfred's fix. They know that they have to keep both Accounting and Advertising happy. So they fix the problem by identifying the user with the method call like so:

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
  IncreaseRectangleSizeByTenPercent(
    rectangles, 
    new User() { Department = Department.Accounting });
}

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles, User user)
{
  foreach(var rectangle in rectangles)
  {
    if (typeof(rectangle) == Rectangle || user.Department == Department.Accounting)
    {
      rectangle.Length = rectangle.Length * 1.1;
    }
    else if (typeof(rectangle) == Square)
    {
      rectangle.Length = rectangle.Length * 1.04880884817;
    }
  }
}

And so on and so forth.

This anecdote is based on real-world situations that face programmers daily. Violations of the Liskov Substitution principle can introduce very subtle bugs that only get picked up years after they're written, by which time fixing the violation will break a bunch of things and not fixing it will anger your biggest client.

There are two realistic ways of fixing this problem.

The first way is to make Rectangle immutable. If the user of Rectangle cannot change the Length and Width properties, this problem goes away. If you want a Rectangle with a different length and width, you create a new one. Squares can inherit from rectangles happily.

The second way is to break the inheritance chain between squares and rectangles. If a square is defined as having a single SideLength property and rectangles have a Length and Width property and there is no inheritance, it's impossible to accidentally break things by expecting a rectangle and getting a square. In C# terms, you could seal your rectangle class, which ensures that all Rectangles you ever get are actually Rectangles.

In this case, I like the "immutable objects" way of fixing the problem. The identity of a rectangle is its length and width. It makes sense that when you want to change the identity of an object, what you really want is a new object. If you lose an old customer and gain a new customer, you don't change the Customer.Id field from the old customer to the new one, you create a new Customer.

Violations of the Liskov Substitution principle are common in the real world, mostly because a lot of code out there is written by people who are incompetent/ under time pressure/ don't care/ make mistakes. It can and does lead to some very nasty problems. In most cases, you want to favour composition over inheritance instead.

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
Stephen
  • 8,800
  • 3
  • 30
  • 43
  • 7
    Liskov is one thing, and storage is another issue. In most implementations, a Square instance inheriting from Rectangle will require space for storing two dimensions, even though only one is needed. – el.pescado - нет войне May 07 '14 at 07:54
  • 2
    What you say is true el.pescado and what you do would depend entirely on your domain. In 99% of cases the storage difference between the two would make no difference whatsoever to the entire size of the application. – Stephen May 07 '14 at 08:12
  • 32
    Brilliant use of a story to illustrate the point – Rory Hunter May 07 '14 at 08:24
  • 2
    @el.pescado Wasting a bit of space is unfortunate, but if it at least worked, that would probably be an acceptable price for the benefits of subtyping and implementation inheritance. Increasing storage by maybe a third (take per-object metadata into account) isn't too bad for most use cases. Memory usage is at best a secondary reason to avoid inheritance in this example. –  May 07 '14 at 10:16
  • 31
    Nice story but I do not agree. The use case was: change the area of a rectangle. The fix should be add an overridable method 'ChangeArea' to rectangle that gets specialized in Square. This would not break the inheritance chain, make explicit what the user wanted to do, and would not have caused the bug introduced by your mentioned fix (which would've been catched in a proper staging area). – Roy T. May 07 '14 at 12:00
  • 35
    @RoyT.: Why should a Rectangle know how to *set* its area? That's a property derived entirely from the length and width. And more to the point, which dimension should it change -- the length, the width, or both? – cHao May 07 '14 at 13:03
  • 36
    @Roy T. It's all very nice to say that you'd have solved the problem differently, but the fact is that this is an example - albeit simplified - of real world situations that developers face on a daily basis when maintaining legacy products. And even if you did implement that method, that won't stop inheritors violating the LSP and introducing bugs akin to this one. This is why pretty much every class in the .NET framework is sealed. – Stephen May 07 '14 at 13:06
  • 2
    I'm just illustrating that the troubles you lay out are quite arbitrary and not just the result from a square being or not being a rectangle. The premise of a lot of people here seems to be that a specialization of a class/interface cannot add constraints and I'm not sure if I can agree with that. I'll try to add an answer in which I try to explain more. – Roy T. May 07 '14 at 14:39
  • 5
    @Stephen: I sort of agree with RoyT: A class should hide something. In the case of Rectangles and Squares, this should probably be their dimensions. It is then their behavior that changes for the different implementations/subtypes. This could be methods like `Draw`, `IncreaseSize`, ... This is also reflected in [TellDontAsk](http://pragprog.com/articles/tell-dont-ask): Don't ask for the length, do a computation and then mutate. Just tell it what you want to achieve! – Laoujin May 07 '14 at 15:59
  • 2
    @Laoujin "A class should hide something." An object should hide something. `Rectangle` as defined here is a concrete data structure. Blame Java for trying to pretend everything is an object. – Doval May 07 '14 at 19:32
  • 1
    @RoyT.: `Rectangle rect = getShapeFromLibraryLoadedAfterCompileTime(); changeArea(rect);` Which overload should I pick for ChangeArea? Should I do runtime introspection to dispatch things which are squares to a different implementation? How should I do that in languages without runtime introspection? If you start requiring knowledge of the specific type instead of the known type, you're breaking fundamental parts of the most useful abstractions ad hoc polymorphism offers us. – Phoshi May 08 '14 at 09:12
  • @Phoshi do you mean this problem: http://pastie.org/9155513 ? It is not specific to this example/problem but I would use basic polymorphism, so rect.changeArea() which will call the right overload instead of a separate method. Not 100% sure what you're getting at here. – Roy T. May 08 '14 at 12:21
  • @RoyT.: It is hardly reasonable to use dynamic dispatch for every single operation which may ever use an object, that'll just end up bloating your shape definition hugely, and it still doesn't solve the problem in the general case, just this specific case. – Phoshi May 08 '14 at 12:27
  • @ColeJohnson that's entirely true, but what is also true is that to "do things properly" after the fact can often cost money through lost sales or a customer base that feels betrayed. As a rule, companies are generally against making decisions that cost money. – Stephen May 09 '14 at 06:54
  • 1
    Immutability doesn't solve your contrived example. Now someone writes a `GiveMeANewRectangleThatsTenPercentBigger(rect r)` function and you still have the same issue. Immutability didn't solve a thing. – Sam Axe May 09 '14 at 08:36
  • 6
    @Dan-o Yes it does solve it. Pass `GiveMeANewRectangleThatsTenPercentBigger` a square and you get back a rectangle 1.1 times the length and the original width, which is exactly what it should do. – Ian Goldby May 09 '14 at 10:40
  • 2
    @RoyT. A specialization can add constraints, but should only do so if the possibility of the constraint is documented at the base class. The point of subtyping is to allow callers to be ignorant of the subtype. This would be possible if Rectangle says SetSize might raise an InvalidSize exception, but not if this is only introduced by the unknown subtype. – nmclean May 09 '14 at 13:15
  • 1
    Is the crux of this violation of LSP that a Rectangle has behavior that a Square cannot? You can mess with a Rectangle's length and width, but you can't do that to a Square. So (forgetting what you learned in first grade) in the limited sense of these classes, **a Square is not a Rectangle**. And that means Square should not be a subtype of Rectangle. – Bob Stein May 09 '14 at 13:38
  • 6
    @BobStein-VisiBone: That's a good way to look at it; more generally, the LSP requires that a subtype never introduce a *restriction* on how an object may be used or violate an *invariant* of a supertype. Stephen's point about immutability is that with immutable data types you do *not* have to forget what you learned in first grade. In first grade you did not learn "a rectangle is a thing whose length and width can be changed"! – Eric Lippert May 09 '14 at 16:34
  • @ColeJohnson Really? I'm hoping that was sarcasm. I know heaps of ridiculously smart developers who only use windows (at least professionally). – Stephen May 10 '14 at 04:28
  • @Stephen As a 3rd solution, wouldn't it fix the problem if Rectangle derived from Square? To me it seems Rectangle is a type of Square. – Vahid Aug 25 '17 at 08:38
  • @Vahid: "All squares are rectangles, but not all rectangles are squares." By LSP, any property provable about a type must also be true of its subtypes. Squares by definition have equal length and width, so subtypes of Square must too. If Rectangle were a subtype of Square, then every Rectangle would have to have length = width...which would make Rectangle equivalent to Square, and thus useless. – cHao Mar 14 '18 at 19:58
33

If all your objects are immutable, there is no problem. Every Square is also a Rectangle. All the properties of a Rectangle are also properties of a Square.

The problem begins when you add the ability to modify the objects. Or really - when you start passing arguments to the object, not just reading property getters.

There are modifications that you can do to a Rectangle that maintain all the invariants of your Rectangle class, but not all Square invariants - like changing the width or height. Suddently the behavior of a Rectangle isn't just its properties, it is also its possible modifications. It's not just what you get out of the Rectangle, it's also what you can put in.

If your Rectangle has a method setWidth that is documented as changing the width and not modifying the height, then Square cannot have a compatible method. If you change the width and not the height, the result is no longer a valid Square. If you chose to modify both width and height of the Square when using setWidth, you are not implementing the specification of Rectangle's setWidth. You just can't win.

When you look at what you can "put into" a Rectangle and a Square, what messages you can send to them, you'll likely find that any message you can validly send to a Square, you can also send to a Rectangle.

It's a matter of co-variance vs. contra-variance.

Methods of a proper subclass, one where instances can be used in all cases where the superclass is expected, requires each method to:

  • Return only values that the superclass would return - that is, the return type must be a subtype of the superclass method's return type. Return is co-variant.
  • Accept all values that the supertype would accept - that is, the argument types must be supertypes of the superclass method's argument types. Arguments are contra-variant.

So, back to Rectangle and Square: Whether Square can be a subclass of Rectangle depends entirely on which methods Rectangle have.

If Rectangle has individual setters for width and height, Square won't make a good subclass.

Likewise, if you make some methods be co-variant in the arguments, like having compareTo(Rectangle) on Rectangle and compareTo(Square) on Square, you will have a problem using a Square as a Rectangle.

If you design your Square and Rectangle to be compatible, it will likely work, but they should be developed together, or I'll bet that it won't work.

lrn
  • 447
  • 3
  • 5
  • _"If all your objects are immutable, there is no problem"_ -- this is apparently irrelevant statement in the context of this question, which explicitly mentions setters for width and height – gnat May 07 '14 at 11:38
  • 11
    I found this interesting, even when it's "apparently irrelevant" – Jesvin Jose May 07 '14 at 12:03
  • 16
    @gnat I'd argue it's relevant because the real value of the question is recognizing when there's a valid subtyping relationship between two types. That depends on which operations the supertype declares, so it's worth pointing out the problem goes away if the mutator methods go away. – Doval May 07 '14 at 12:52
  • 1
    @gnat also, setters are *mutators*, so lrn is essentially saying, *"Don't do that and it's not a problem."* I happen to agree with immutability for simple types, but you make a good point: For complex objects, the problem is not so simple. – Patrick M May 07 '14 at 15:50
  • 1
    Consider it this way, what is the behavior guaranteed by 'Rectangle' class ? That you can change width and height INDEPENDENT of each other. (i.e. setWidth and setHeight) method. Now if Square is derived from Rectangle, Square has to guarantee this behavior. Since square cannot guarantee this behavior, its a bad inheritance. However, if setWidth/setHeight methods are removed from Rectangle class, then there is no such behavior and hence you can derive Square class from Rectangle. – Nitin Bhide May 09 '14 at 13:59
  • @gnat I think that statement is the correct answer, because I consider "mutable square" as a different type than "immutable square", so the OP's question appears as wrong or incomplete, and the point about immutability as a corollary. – ignis May 11 '14 at 18:45
19

There are lots of good answers here; Stephen's answer in particular does a good job of illustrating why violations of the substitution principle lead to real-world conflicts between teams.

I thought I might talk briefly about the specific problem of rectangles and squares, rather than using it as a metaphor for other violations of the LSP.

There is an additional problem with square-is-a-special-kind-of-rectangle that seldom gets mentioned, and that is: why are we stopping with squares and rectangles? If we are willing to say that a square is a special kind of rectangle then surely we should also be willing to say:

  • A square is a special kind of rhombus -- it's a rhombus with square angles.
  • A rhombus is a special kind of parallelogram -- it's a parallelogram with equal sides.
  • A rectangle is a special kind of parallelogram -- it's a parallelogram with square angles
  • A rectangle, square and parallelogram are all a special kind of trapezoid -- they are trapezoids with two sets of parallel sides
  • All of the above are special kinds of quadrilaterals
  • All of the above are special kinds of planar shapes
  • And so on; I could keep going for some time here.

What on earth should all of the relationships be here? Class-inheritance-based languages like C# or Java were not designed to represent these sorts of complex relationships with multiple different kinds of constraints. It's best to simply avoid the question entirely by not trying to represent all of these things as classes with subtyping relationships.

Eric Lippert
  • 45,799
  • 22
  • 87
  • 126
  • 3
    If shapes objects are immutable, then one could have an `IShape` type which includes a bounding box, and can be drawn, scaled, and serialized, and have a `IPolygon` subtype with a method to report the number of vertices and a method to return an `IEnumerable`. One could then have `IQuadrilateral` subtype which derives from `IPolygon`, `IRhombus` and `IRectangle`, derive from that, and `ISquare` derive from `IRhombus` and `IRectangle`. Mutability would throws everything out the window, and multiple inheritance doesn't work with classes, but I think it's fine with immutable interfaces. – supercat Nov 17 '14 at 23:10
  • I effectively disagree with Eric here (not enough for a -1 though!). All those relationships are (possibly) relevant, as @supercat mentions; it's just a YAGNI issue: you don't implement it until you need it. – Mark Hurd May 13 '15 at 02:13
  • Very good answer! Should be way higher. – andrew.fox Jan 08 '16 at 19:57
  • 1
    @MarkHurd - it's not a YAGNI issue: the inheritance-based hierarchy proposed is shaped like the described taxonomy, but it cannot be written to guarantee the relationships that define it. How does `IRhombus` guarantee that all `Point` returned from the `Enumerable` defined by `IPolygon` correspond to edges of equal lengths? Because the implementation of the `IRhombus` interface alone does not guarantee that a concrete object is-a rhombus, inheritance cannot be the answer. – A. Rager Jun 28 '17 at 16:18
14

From a mathematical perspective, a square is-a rectangle. If a mathematician modifies the square so it no longer adheres to the square contract, it changes into a rectangle.

But in OO design, this is a problem. An object is what it is, and this includes behaviors as well as state. If I hold a square object, but someone else modifies it to be a rectangle, that violates the square's contract through no fault of my own. This causes all sorts of bad things to happen.

The key factor here is mutability. Can a shape change once it is constructed?

  • Mutable: if shapes are allowed to change once constructed, a square cannot have an is-a relationship with rectangle. The contract of a rectangle includes the constraint that opposite sides must be of equal length, but adjacent sides need not be. The square must have four equal sides. Modifying a square through a rectangle interface can violate the square contract.

  • Immutable: if shapes cannot change once constructed, then a square object must also always fulfill the rectangle contract. A square can have an is-a relationship with rectangle.

In both cases it is possible to ask a square to produce a new shape based on its state with one or more changes. For example, one could say "create a new rectangle based on this square, except that opposing sides A and C are twice as long." Since a new object is being constructed, the original square continues to adhere to its contracts.

  • 1
    `This is one of those cases where the real world is not able to be modeled in a computer 100%`. Why so? We can still have a functional model of a square and a rectangle. The only consequence is that we have to look for a simpler construct to abstract over those two objects. – Simon Bergot May 07 '14 at 06:16
  • 6
    There is more in common between rectangles and squares than that. The problem is that the identity of a rectangle and the identity of a square are its side lengths (and the angle at each intersection). The best solution here is to make squares inherit from rectangles, but make both immutable. – Stephen May 07 '14 at 07:29
  • 3
    @Stephen Agreed. Actually, making them immutable is the sensible thing to do regardless of subtyping problems. There's no reason to make them mutable - it's no harder to construct a new square or rectangle than to mutate one, so why open that can of worms? Now you don't have to worry about aliasing/side effects and you can use them as keys to maps/dicts if needed. Some will say "performance", to which I'll say "premature optimization" until they've actually measured and proved that the hot spot is in the shape code. – Doval May 07 '14 at 11:35
  • Sorry guys, it was late and I was super tired when I wrote the answer. I rewrote it to say what I really meant, the crux of which is mutability. –  May 07 '14 at 14:25
13

And why would Square be usable anywhere you expect a rectangle?

Because that's part of what being a subtype means (see also: Liskov substitution principle). You can do, need to be able to do this:

Square s = new Square(5);
Rect r = s;
doSomethingWith(r); // written assuming a Rect, actually calls Square methods

You actually do this all the time (sometimes even more implicitly) when using OOP.

and if we over-ride the SetWidth and SetHeight methods for Square than why would there be any issue?

Because you can't sensibly override those for Square. Because a square can't "be re-sized like any rectangle". When the height of a rectangle changes, the width remains the same. But when the height of a square changes, the width must change accordingly. The issue isn't just being re-sizable, it's being re-sizable in both dimensions independently.

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
  • In a lot of languages, you don't even need the `Rect r = s;` line, you can just `doSomethingWith(s)` and the runtime will use any calls on `s` to resolve to any virtual `Square` methods. – Patrick M May 07 '14 at 15:48
  • 1
    @PatrickM You don't need it in any sane language that has subtyping. I included that line for exposition, to be explicit. –  May 07 '14 at 15:51
  • So override `setWidth` and `setHeight` to change both the width and the height. – ApproachingDarknessFish May 07 '14 at 16:40
  • @ValekHalfHeart That is precisely the option I am considering. –  May 07 '14 at 16:43
  • 8
    @ValekHalfHeart: That's precisely the violation of Liskov Substitution Principle that will come haunt you and make you spend several sleepless nights trying to find a strange bug two years later when you've forgotten how the code was supposed to work. – Jan Hudec May 07 '14 at 21:27
  • For example if I decide to increase both the width and the height of an object by 10 percent, and applied to a Square increasing the width also increases the height, and then increasing the height also increases the width, increasing both by 21%. The problem is that simple examples from the real world that try to explain inheritance often just don't work in real life. – gnasher729 May 08 '14 at 18:46
9

Subtyping is about behavior.

For type B to be a subtype of type A, it must support every operation that type A supports with the same semantics (fancy talk for "behavior"). Using the rationale that every B is a A does not work - behavior compatibility has the final say. Most of the time "B is a kind of A" overlaps with "B behaves like A", but not always.

An example:

Consider the set of real numbers. In any language, we can expect them to support the operations +, -, *, and /. Now consider the set of positive integers ({1, 2, 3, ...}). Clearly, every positive integer is also a real number. But is the type of positive integers a subtype of the type of real numbers? Let's look at the four operations and see if positive integers behave the same way as real numbers:

  • +: We can add positive integers without problems.
  • -: Not all subtractions of positive integers result in positive integers. E.g. 3 - 5.
  • *: We can multiply positive integers without problems.
  • /: We can't always divide positive integers and get a positive integer. E.g. 5 / 3.

So despite positive integers being a subset of real numbers, they're not a subtype. A similar argument can be made for integers of finite size. Clearly every 32-bit integer is also a 64-bit integer, but 32_BIT_MAX + 1 will give you different results for each type. So if I gave you some program and you changed the type of every 32-bit integer variable to 64-bit integers, there's a good chance the program will behave differently (which almost always means wrongly).

Of course, you could define + for 32-bit ints so that the result is a 64-bit integer, but now you'll have to reserve 64 bits of space every time you add two 32-bit numbers. That may or may not be acceptable to you depending on your memory needs.

Why does this matter?

It's important for programs to be correct. It's arguably the most important property for a program to have. If a program is correct for some type A, the only way to guarantee that program will continue to be correct for some subtype B is if B behaves like A in every way.

So you have the type of Rectangles, whose specification says its sides can be changed independently. You wrote some programs that use Rectangles and assume the implementation follows the specification. Then you introduced a subtype called Square whose sides can't be resized independently. As a result, most programs that resize rectangles will now be wrong.

Doval
  • 15,347
  • 3
  • 43
  • 58
9

What you're describing runs afoul of what's called the Liskov Substitution Principle. The basic idea of the LSP is that whenever you use an instance of a particular class, you should always be able to swap in an instance of any subclass of that class, without introducing bugs.

The Rectangle-Square problem isn't really a very good way to introduce Liskov. It tries to explain a broad principle using an example that is actually quite subtle, and runs afoul of one of the most common intuitive definitions in all of mathematics. Some call it the Ellipse-Circle problem for that reason, but it's only slightly better as far as this goes. A better approach is to take a slight step back, using what I call the Parallelogram-Rectangle problem. This makes things much easier to understand.

A parallelogram is a quadrilateral with two pairs of parallel sides. It also has two pairs of congruent angles. It's not hard to imagine a Parallelogram object along these lines:

class Parallelogram {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};
    function setSideA(newLength) {};
    function setSideB(newLength) {};
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

One common way to think of a rectangle is as a parallelogram with right angles. At first glance, this might seem to make Rectangle a good candidate for inheriting from Parallelogram, so that you can reuse all that yummy code. However:

class Rectangle extends Parallelogram {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};
    function setSideA(newLength) {};
    function setSideB(newLength) {};

    /* BUG: Liskov violations ahead */
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

Why do these two functions introduce bugs in Rectangle? The problem is that you can't change the angles in a rectangle: they're defined as always being 90 degrees, and so this interface doesn't actually work for Rectangle inheriting from Parallelogram. If I swap a Rectangle into code that expects a Parallelogram, and that code tries to change the angle, there will almost certainly be bugs. We've taken something that was writeable in the subclass and made it read-only, and that's a Liskov violation.

Now, how does this apply this to Squares and Rectangles?

When we say that you can set a value, we generally mean something a little stronger than just being able to write a value into it. We imply a certain degree of exclusivity: if you set a value, then barring some extraordinary circumstances, it will stay at that value until you set it again. There are a lot of uses for values that can be written to but do not stay set, but there are also many cases that depend on a value staying where it is once you set it. And that is where we run into another problem.

class Square extends Rectangle {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};

    /* BUG: More Liskov violations */
    function setSideA(newLength) {};
    function setSideB(newLength) {};

    /* Liskov violations inherited from Rectangle */
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

Our Square class inherited bugs from Rectangle, but it has some new ones. The issue with setSideA and setSideB is that neither of these is truly settable anymore: you can still write a value into either one, but it will change out from under you if the other one is written to. If I swap this for a Parallelogram in code that depends on being able to set sides independently of one another, it's going to freak out.

That's the issue, and it's why there's a problem with using Rectangle-Square as an introduction to Liskov. Rectangle-Square depends on the difference between being able to write to something and being able to set it, and that's a much more subtle difference than being able to set something versus having it be read-only. Rectangle-Square still has value as an example, because it documents a fairly common gotcha that has to be watched out for, but it shouldn't be used as an introductory example. Let the learner get some grounding in the basics first, and then throw something harder at them.

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
The Spooniest
  • 2,160
  • 12
  • 9
6

If a Square is a type of Rectangle than why cant a Square inherit from a Rectangle? Or why is it a bad design?

First things first, ask yourself why you think a square is a rectangle.

Of course most people learnt that in primary school, and it would seem obvious. A rectangle is a 4 sided shape with 90 degree angles, and a square fulfils all those properties. So isn't a square a rectangle?

The thing though is that it all depends on what is your initial criteria for grouping objects, what context are you looking at these objects. In geometry shapes are classified based on the properties of their points, lines and angels.

So before you even say "a square is a type of rectangle" you first have to ask yourself, is this based on criteria I care about.

In the vast majority of cases it isn't going to be what you care about at all. The majority of systems that model shapes, such as GUIs, graphics and video games, are not primarily concerned with the geometric grouping of an object but it is behaviour. Have you ever worked on a system that it mattered that a square was a type of rectangle in the geometric sense. What would that even give you, knowing that it has 4 sides and 90 degree angles?

You are not modelling a static system, you are modelling a dynamic system where things are going to happen (shapes are going to be created, destroyed, altered, drawn etc). In this context you care about shared behaviour between objects, because your primary concern is what you can do with a shape, what rules have to be maintained to still have a coherent system.

In this context a square is most definitely not a rectangle, because the rules that govern how the square can be altered are not the same as the rectangle. So they are not the same type of thing.

In which case don't model them as such. Why would you? It gains you nothing other than an unnecessary restriction.

It would only be usable if we create the Square object, and if we override the SetWidth and SetHeight methods for Square than why would there be any issue?

If you do that though you are practically stating in code that they are not the same thing. Your code would be saying a square behaves this way and a rectangle behaves that way but they are still the same.

They clearly aren't the same in the context that you care about because you just defined two different behaviours. So why pretend they are the same if they are only similar in a context you don't care about?

This highlights a significant problem when developers come to a domain that they wish to model. It is so important to clarify what context you are interested in before you start thinking about the objects in the domain. What aspect are you interested in. Thousands of years ago the Greeks cared about the shared properties of the lines and angels of shapes, and grouped them based on these. That doesn't mean you are forced to continue that grouping if it isn't what you care about (which in 99% of the time modelling in software you won't care about).

A lot of the answers to this question focus on sub-typing being about grouping behaviour 'cause them the rules.

But it is so important to understand that you aren't doing this just to follow the rules. You are doing this because in the vast majority of cases this is what you actually care about as well. You don't care if a square and rectangle share the same internal angels. You care about what they can do while still being squares and rectangles. You care about the behaviour of the objects because you are modelling a system that is focused on changing the system based on the rules of the behaviour of the objects.

Cormac Mulhall
  • 5,032
  • 2
  • 19
  • 19
  • If variables of type `Rectangle` are only used to represent *values*, then it may be possible for a class `Square` to inherit from `Rectangle` and fully abide by its contract. Unfortunately, many languages don't make any distinction between variables that encapsulate values and those which identify entities. – supercat May 08 '14 at 22:45
  • Possibly, but then why bother in the first place. The point of the rectangle/square problem is not to try and figure out how to make the "a square is a rectangle" relationship work, but rather to realize that the relationship doesn't actually exist in the context that you are using the objects (behaviorally), and as a warning about not super imposing irrelevant relationships onto your domain. – Cormac Mulhall May 09 '14 at 09:31
  • Or to put it another way: Do not try and bend the spoon. That's impossible. Instead only try to realize the truth, that there is no spoon. :-) – Cormac Mulhall May 12 '14 at 08:39
  • 1
    Having a immutable `Square` type which inherits from an immutable `Rectnagle` type could be useful if there were some kinds of operations which could only be done upon squares. As a realistic example of the concept, consider a `ReadableMatrix` type [base type a rectangular array which might be stored various ways, including sparsely], and a `ComputeDeterminant` method. It might make sense to have `ComputeDeterminant` work only with a `ReadableSquareMatrix` type that's derived from `ReadableMatrix`, which I would consider to be an example of a `Square` deriving from a `Rectangle`. – supercat May 12 '14 at 13:23
5

If a Square is a type of Rectangle than why cant a Square inherit from a Rectangle?

The problem lies in thinking that if things are related in some way in reality, they must be related in exactly the same way after modelling.

The most important thing in the modelling is to identify the common attributes and the common behaviours, define them in the basic class and add additional attributes in the child classes.

The problem with your example is, that's completely abstract. As long as noone knows, what you plan to use that classes for, it's hard to guess what design you should made. But if you really want to have only height, width and resize, it would be more logical to:

  • define Square as base class, with width parameter and resize(double factor) resizing the width by the given factor
  • define Rectangle class and the subclass of Square, because it adds another attribute, height, and overrides its resize function, which calls super.resize and then resizes the height by the given factor

From the programming point of view, there's nothing in Square, that Rectangle doesn't have. There's no sense of making a Square as the subclass of Rectangle.

  • +1 Just because a square is a special kind of rect in mathematics, doesn't mean it's the same in OO. – Lovis May 08 '14 at 16:27
  • 1
    A square is a square and a rectangle is a rectangle. The relationships between them should hold in modeling as well, or you have a pretty poor model. The real issues are: 1) if you make them mutable, you're no longer modeling squares and rectangles; 2) assuming that just because some "is a" relationship holds between two kinds of objects, you can substitute one for the other indiscriminately. – Doval May 09 '14 at 12:00
4

Because by LSP, creating inheritance relation between the two and overriding setWidth and setHeight to ensure square has both as same introduces confusing and non-intuitive behavior. Lets say we have a code:

Rectangle r = createRectangle(); // create rectangle or square here
r.setWidth(10);
r.setHeight(20);
print(r.getWidth()); // expect to print 10
print(r.getHeight()); // expect to print 20

But if the method createRectangle returned Square, because it is possible thanks to Square inheriting from Rectange. Then the expectations get broken. Here, with this code, we expect setting width or height will only cause change to width or height respectively. The point of OOP is that when you work with the superclass, you have zero knowledge of any subclass under it. And if subclass changes the behavior so that it goes against expectations we have about superclass, then there is high chance that bugs will occur. And those kind of bugs are both hard to debug and fix.

One of the major ideas about OOP is that it is behavior, not data that is inherited (which is also one of the major misconceptions IMO). And if you look at square and rectangle, they have no behavior themselves that we could relate in inheritance relation.

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
Euphoric
  • 36,735
  • 6
  • 78
  • 110
2

What LSP says is that anything that inherits from Rectangle must be a Rectangle. That is, it should do whatever a Rectangle does.

Probably the documentation for Rectangle is written to say that the behaviour of a Rectangle named r is as follows:

r.setWidth(10);
r.setHeight(20);
print(r.getWidth());  // prints 10

If your Square doesn't have that same behaviour then it doesn't behave like a Rectangle. So LSP says it must not inherit from Rectangle. The language can't enforce this rule, because it can't stop you doing something wrong in a method override, but that doesn't mean "it's OK because the language lets me override the methods" is a convincing argument for doing it!

Now, it would be possible to write the documentation for Rectangle in such a way that it doesn't imply that the above code prints 10, in which case maybe your Square could be a Rectangle. You might see documentation that says something like, "this does X. Furthermore, the implementation in this class does Y". If so then you have a good case for extracting an interface from the class, and distinguishing between what the interface guarantees, and what the class guarantees in addition to that. But when people say "a mutable square is not a mutable rectangle, whereas an immutable square is an immutable rectangle", they're basically assuming that the above is indeed part of the reasonable definition of a mutable rectangle.

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
Steve Jessop
  • 5,051
  • 20
  • 23
  • this seems to merely repeat points explained in an [answer posted 5 hours ago](http://programmers.stackexchange.com/a/238216/31260) – gnat May 07 '14 at 16:29
  • @gnat: would you prefer me to edit that other answer down to approximately this brevity? ;-) I don't think I can without removing points that other answerer presumably feels are necessary to answer the question and I feel are not. – Steve Jessop May 07 '14 at 16:31
  • http://meta.workplace.stackexchange.com/questions/2562/when-there-are-many-answers-already-help-me-check-that-mine-wont-repeat-others – gnat May 07 '14 at 16:50
1

Subtypes and, by extension, OO programming, often rely on the Liskov Substitution Principle, that any value of type A can be used where a B is required, if A <= B. This is pretty much an axiom in OO architecture, ie. it is assumed that all subclasses will have this property (and if not, the subtypes are buggy and need to be fixed).

However, it turns out that this principle is either unrealistic/unrepresentative of most code, or indeed impossible to satisfy (in non-trivial cases)! This problem, known as the square-rectangle problem or the circle-ellipse problem ( http://en.wikipedia.org/wiki/Circle-ellipse_problem ) is one famous example of how difficult it is to fulfil.

Note that we could implement more-and-more observationally-equivalent Squares and Rectangles, but only by throwing away more and more functionality until the distinction is useless.

As an example, see http://okmij.org/ftp/Computation/Subtyping/

Warbo
  • 1,205
  • 7
  • 11