Card 05/ 09
Liskov Example: Why a Square Is Not a Rectangle in Code
Set the width to 5 and the height to 4, and the area comes back 16.
A square is a rectangle in geometry, so making Square a subclass of Rectangle looks right. This example shows why it breaks substitution in code: a rectangle's width and height can change independently, and a square's cannot. Predict the result before you read the output.
Rectangle has set_width, set_height and area. Square keeps its sides equal by overriding both setters to set both sides. The function resize_and_measure was written for rectangles: it sets the width to 5, then the height to 4, and returns the area.
Predict: what does resize_and_measure return for a Rectangle, and for a Square?
A rectangle returns 20, which is what the function's author expected. A square returns 16: setting the height to 4 also set the width to 4, undoing the first call.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def set_width(self, width):
self.width = width
def set_height(self, height):
self.height = height
def area(self):
return self.width * self.height
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
def set_width(self, width):
self.width = width
self.height = width
def set_height(self, height):
self.width = height
self.height = height
def resize_and_measure(shape):
shape.set_width(5)
shape.set_height(4)
return shape.area()
print(f"Rectangle: {resize_and_measure(Rectangle(1, 1))}")
print(f"Square: {resize_and_measure(Square(1))}")Rectangle: 20
Square: 16Every line of Square is correct for a square. The failure is in the relationship. Rectangle carried an unwritten invariant: setting one side leaves the other alone. Any code that resizes a rectangle relies on it. Square cannot keep that invariant and stay a square, so it breaks the third of the Liskov rules.
The lesson is that "is a" in the real world does not decide inheritance in code. What decides it is whether the subtype can keep the parent's behaviour, and here it depends on the parent being mutable. If shapes were immutable, with no setters, a square could be a rectangle safely, because there would be no independent resize for it to break. A common fix is exactly that: make shapes immutable, or make Square and Rectangle siblings under a Shape that only promises area.
← → move through sections and cards