August 10, 2007
More on LiskovLast month I wrote about the Liskov Substitution Principle and how I thought it was bogus that it implied a square was not a rectangle. I had several followup comments about that where people started about by saying "Well, of course" but then when I asked for specifics they all stopped frustratingly short of explaining why.
A mathematician named Charles Wells, who wrote a couple of books with my father, wrote a blog post where he used my post as a launching post to discuss special cases in mathematics. He pointed out that if you Google "square rectangle Liskov", then you will find lots of discussion about this.
Well, I did. The first thing it finds is evidently the canonical Liskov-based square-is-not-rectangle (PDF) by Robert Martin. He makes the exact argument that I talked about before, that calling SetWidth() and then SetHeight() on a square won't behave as expected. James Waletzky, who was the person from whom I originally heard the argument, is a Robert Martin fan, so no surprise there. You also find a bunch of people repeating Martin's argument. There was a different one I found a few days ago related to a method that returned the area of the rectangle, which I can't find now, but it's basically the same thing.
I still don't like these. Because remember A SQUARE IS A RECTANGLE. I mean really, it is. Although you may win points from your object-oriented friends for "proving" something so non-intuitive, I fail to see how it makes programming easier if you violate someone's real-world model in such a basic way.
Right, but what about SetHeight()? Well, what I think now is that applying Liskov to this particular class design shows that designing a Rectangle class with Set() methods is bad design. A rectangle is simple enough that you can simply define it with constructors that take height and width, methods to get the height and width, and that's it. 99% of time that's how you want to use such a class, as a way to pass around information about a rectangle, and if you need to change the dimensions, just construct a new rectangle.
Then with your Square subclass you are free to add a constructor which takes only a single parameter, and override the base rectangle constructor so it fails if the height and width are not equal. This is fine since the code that is constructing the class knows the exact type it is and should therefore be expected to handle the case where they construct a Square by calling the two-parameter constructor from Rectangle and pass in a height and width that aren't equal.
So while Liskov has demonstrated why the Rectangle with Set() methods is bad design, a Square can still be a Rectangle.
Posted by AdamBa at August 10, 2007 07:14 AM
TrackBack URL for this entry:
I disagree. I think a Square should be a subclass of circle. :)
Posted by: Phil at August 10, 2007 08:14 AM
It seems to me that you can still have SetHeight() and SetWidth(). For a square, changing one changes the other.
It's no different than having a subclass of Rectangle that enforces a constraint that it always has a 16:9 aspect ratio, for example. Changing either height or width would set the other as appropriate.
Posted by: Buck Hodges at August 11, 2007 05:52 PM
Buck, the problem with that is if someone is using a Square but thinks it is a Rectangle, then the Set() calls won't have the result they expect. This is the argument that Martin and others have laid out.
Posted by: Adam Barr at August 12, 2007 09:51 AM
So, to avoid unexpected results, you are making Rectangle (and Square) immutable? It sounds like a good motive, but if you apply the same in the general case, it sounds like a high price to pay.
In practice, I would probably choose not to create a Square class at all. Instead, I would add a single argument constructor, a SetSize method, and a boolean IsSquare function to my Rectangle class.
In this way, a Square would be more of a state of a mutable Rectangle than a class by itself.
I understand that this way, client code would need to take more responsibility by keeping its Squares square ;)
Posted by: DiegoV at August 19, 2007 02:16 AM