Language: English O’zbek
Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types without altering program correctness.
If code works with a base class, it must continue to work correctly with any subclass — no surprises, no broken contracts.
Diagram
Violation
In violation.py Square inherits from Rectangle and overrides the setters to keep width and height equal. This introduces a side-effect that violates the behavioral contract of Rectangle:
class Square(Rectangle):
def set_width(self, width: float) -> None:
self._width = width
self._height = width # side-effect!
def set_height(self, height: float) -> None:
self._width = height # side-effect!
self._height = height
Any code that independently sets width and height on a Rectangle will get wrong results when handed a Square:
def resize_rectangle(rect: Rectangle, width: float, height: float) -> float:
rect.set_width(width)
rect.set_height(height)
expected = width * height
actual = rect.area()
assert actual == expected # fails for Square!
Correct
In correct.py both Rectangle and Square inherit from a common Shape abstraction. Each shape owns its geometry without pretending to be something it is not:
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
class Rectangle(Shape):
def __init__(self, width: float, height: float) -> None:
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
class Square(Shape):
def __init__(self, side: float) -> None:
self.side = side
def area(self) -> float:
return self.side ** 2
Both can be passed anywhere a Shape is expected — no behavioral surprises.