Reputation: 454
The Liskov Substitution Principle (LSP) imposes four behavioral requirements on subtypes (directly from Wikipedia):
There is the somewhat famous square-rectangle (or circle-ellipse) problem that breaks the LSP, but I'd like to understand which of the conditions it is actually breaking.
The problem is as follows: we have a Square
which is-a Rectangle
. We can set the width, w
, and height, h
, of Rectangle
, which in the case of a Square
are overridden to modify both width and height at the same time (i.e., Square.setWidth
is implemented to set both w
and h
to the same value).
Now, we introduce another class method squeeze
which changes h
to h/2
. If this is called on Square
, the invariant of w==h
is violated.
Intuitively, then, we'd be breaking the invariant condition from above, but it seems that the problem is that we have strengthened an invariant in the subtype (the original invariant being only that w,h >= 0
).
We also haven't weakened a postcondition. Again, if anything we must have strengthened the postcondition that w==h
after any operation on Square
that is not there for Rectangle
.
I don't see how preconditions are relevant.
For the history constraint, we have only used class methods.
Upvotes: 1
Views: 83
Reputation: 1461
The Liskov Substitution Principle (LSP):
Subclasses should be substitutable for their base classes.
This means that a user of a base class should continue to function properly if a derivative of that base class is passed to it.
The user assumes that if you provide him with methods for setting the width and height, he will be able to change them independently. But if the user works with a Square
instead of a Rectangle
, setting the height will implicitly change the width, and trying to get the width, the user will get a value that is not what he would expect. This is a violation of the principle.
Bertrand Meyer in Design by Contract says:
…when redefining a routine [in a derivative], you may only replace its precondition by a weaker one, and its postcondition by a stronger one.
This means you can only reduce the set of preconditions and only extend the set of postconditions.
You can think of the set of postconditions as a set of tests, where all tests for the base type must pass for an object of the subtype.
// postcondition
void test(Rectangle rectangle) {
rectangle.setWidth(1);
rectangle.setHight(1);
rectangle.setWidth(3);
// is violated for a square
assert rectangle.getHight() == 1;
}
Upvotes: 0
Reputation: 882326
The Liskov substitution principal is problematic if you treat a square/circle as if it was a rectangle/ellipse in that the two things act differently.
For example, if you change the height of a 5x5 rectangle to 6, it becomes a 5x6 rectangle. If you do the same thing to a square, it becomes a 6x6 square. So, if you have a test case that checks width is unchanged after changing height, it will act differently between the two shapes:
def test_case_1():
r = rectangle(5, 5)
r.set_height(6)
assert r.get_width() == 5 # OKAY
def test_case_2():
r = square(5)
r.set_height(6)
assert r.get_width() == 5 # FAILS
This would be a pre-condition strengthening violation. Since a rectangle has a pre-condition where the width is independent of the height, the square violates this by tying them together.
Hence, according to LSP, a square is not a special type of rectangle. For my purposes, I wouldn't even define a square. I would instead define a rectangle with three member functions that can set height, width, or both. Then the problem goes away :-)
Upvotes: 0