Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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.

share|improve this question
14  
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. –  eBusiness May 7 at 8:48
2  
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 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 at 10:25
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 7 at 11:31
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 7 at 14:37
show 11 more comments

13 Answers 13

up vote 147 down vote accepted

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 so much real-world code that's currently out there. 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.

share|improve this answer
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 7 at 7:54
16  
Brilliant use of a story to illustrate the point –  Rory Hunter May 7 at 8:24
17  
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 at 12:00
23  
@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 at 13:03
23  
@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 at 13:06
show 15 more comments

Perhaps because a Square isn't a special type of Rectangle, Rectangle is a special type of Square...

By this I mean that even with a mutable class, you can easily make a Rectangle class where all of the behavior/properties of a Square are maintained. Setting the size, orientation, area, center point...

Basicaly, while it can have looser conditions than a Square, AS a Square it doesn't have to expose that fact. You run into problems with treating a Square as a Rectangle, because you can't avoid the fact that if you act upon a Square as if it was a Rectangle you have operations with unexpected side effects from the POV of the rectangle. You override setWidth/setHeight and suddenly r.setWidth(10); r.setHeight(15); print r.area(); does not equal 150. But Square doesn't have setWidth it has setSides so `s.setSides(10); print s.area();' prints 100 and all is good.

You really need to consider WHAT you are modelling...

share|improve this answer
add comment

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.

share|improve this answer
add comment

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.

share|improve this answer
add comment

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/

share|improve this answer
add comment

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.

share|improve this answer
    
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 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 9 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. :-) –  Cormac Mulhall May 12 at 8:39
    
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 at 13:23
add comment

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.

share|improve this answer
    
this seems to merely repeat points explained in an answer posted 5 hours ago –  gnat May 7 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 7 at 16:31
    
add comment

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.

  • Mutability: if shapes are allowed to change once constructed, a square cannot have an is-a relationship with rectangle.
  • Immutability: if shapes cannot change once constructed, then a square object must also always fulfill the rectangle contract, so square can have an is-a relationship with rectangle.

In both cases it is possible to ask a square to produce a new shape that is identical except for some difference. For example, two of its opposing sides are longer, which would produce a rectangle. The original object is still a square, but one using it could have a second object, a rectangle.

share|improve this answer
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 May 7 at 6:16
5  
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 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 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. –  John Gaughan May 7 at 14:25
add comment

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.

share|improve this answer
    
+1 Just because a square is a special kind of rect in mathematics, doesn't mean it's the same in OO. –  Don L. May 8 at 16:27
    
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 at 12:00
add comment

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.

share|improve this answer
    
Dear downvoter: kindly leave suggestions for improvement. –  Doval May 7 at 13:48
add comment

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.

share|improve this answer
    
"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 at 11:38
7  
I found this interesting, even when it's "apparently irrelevant" –  aitchnyu May 7 at 12:03
12  
@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 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 at 15:50
    
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 9 at 13:59
show 1 more comment

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.

share|improve this answer
add comment

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.

share|improve this answer
    
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 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. –  delnan May 7 at 15:51
    
So override setWidth and setHeight to change both the width and the height. –  ValekHalfHeart May 7 at 16:40
    
@ValekHalfHeart That is precisely the option I am considering. –  delnan May 7 at 16:43
7  
@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 at 21:27
show 1 more comment

protected by Community May 7 at 11:53

Thank you for your interest in this question. Because it has attracted low-quality answers, posting an answer now requires 10 reputation on this site.

Would you like to answer one of these unanswered questions instead?

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