124

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.

16
  • 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. May 7, 2014 at 8: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 7, 2014 at 9: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 7, 2014 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 7, 2014 at 11:29
  • 6
    @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 7, 2014 at 11:31

12 Answers 12

208

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.

22
  • 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. May 7, 2014 at 7:54
  • 32
    Brilliant use of a story to illustrate the point May 7, 2014 at 8:24
  • 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 7, 2014 at 12:00
  • 36
    @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 7, 2014 at 13:03
  • 37
    @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 7, 2014 at 13:06
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.

6
  • "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 7, 2014 at 11:38
  • 11
    I found this interesting, even when it's "apparently irrelevant" May 7, 2014 at 12:03
  • 17
    @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 7, 2014 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 7, 2014 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. May 9, 2014 at 13:59
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.

4
  • 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<Point>. 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, 2014 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, 2015 at 2:13
  • Very good answer! Should be way higher.
    – andrew.fox
    Jan 8, 2016 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<Point> 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, 2017 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.

5
  • 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. May 7, 2014 at 6: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 7, 2014 at 7: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 7, 2014 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.
    – user22815
    May 7, 2014 at 14:25
  • This is similar to another question about whether a list of Dogs is a list of Animals... and the answer is yes, as long as the contents are immutable. Once you allow the properties to be changed, such equivalence breaks down. Oct 20, 2023 at 0:38
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.

6
  • 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 7, 2014 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.
    – user7043
    May 7, 2014 at 15:51
  • So override setWidth and setHeight to change both the width and the height. May 7, 2014 at 16:40
  • @ValekHalfHeart That is precisely the option I am considering.
    – user7043
    May 7, 2014 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 7, 2014 at 21:27
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.

0
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.

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.

4
  • 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 8, 2014 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. May 9, 2014 at 9: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. :-) May 12, 2014 at 8: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, 2014 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.

2
  • +1 Just because a square is a special kind of rect in mathematics, doesn't mean it's the same in OO.
    – Lovis
    May 8, 2014 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 9, 2014 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.

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.

3
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/

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