Ответ
Принцип подстановки Лисков (LSP) гласит, что объекты в программе должны быть заменяемыми экземплярами их подтипов без изменения корректности программы. Нарушение LSP происходит, когда подкласс изменяет ожидаемое поведение базового класса, делая его несовместимым с кодом, который работает с базовым типом.
Пример нарушения LSP (Python):
Рассмотрим классический пример с Rectangle и Square:
class Rectangle:
def __init__(self, width: int, height: int):
self._width = width
self._height = height
@property
def width(self) -> int:
return self._width
@width.setter
def width(self, value: int):
self._width = value
@property
def height(self) -> int:
return self._height
@height.setter
def height(self, value: int):
self._height = value
def area(self) -> int:
return self._width * self._height
class Square(Rectangle):
def __init__(self, side: int):
super().__init__(side, side)
@Rectangle.width.setter
def width(self, value: int):
# Нарушение: изменение ширины влияет на высоту
self._width = value
self._height = value
@Rectangle.height.setter
def height(self, value: int):
# Нарушение: изменение высоты влияет на ширину
self._width = value
self._height = value
def test_rectangle_area(rect: Rectangle):
# Ожидаем, что изменение ширины не повлияет на высоту
rect.width = 5
rect.height = 4
# Для Rectangle: 5 * 4 = 20
# Для Square: 5 * 5 = 25 (после rect.width = 5), затем 4 * 4 = 16 (после rect.height = 4)
assert rect.area() == 20 # Это утверждение будет ложным для Square!
# Тестирование с Rectangle - работает корректно
rectangle_obj = Rectangle(10, 2)
test_rectangle_area(rectangle_obj) # OK, area = 20
# Тестирование с Square - нарушает LSP
square_obj = Square(10)
try:
test_rectangle_area(square_obj) # AssertionError
except AssertionError:
print("Нарушение LSP: Square не может быть подставлен вместо Rectangle без изменения поведения.")
Объяснение нарушения:
Класс Square наследуется от Rectangle, но изменяет его фундаментальное поведение: при изменении ширины или высоты Square обе стороны устанавливаются одинаковыми. Это нарушает инвариант Rectangle, который предполагает независимое изменение сторон.
Функция test_rectangle_area, разработанная для работы с Rectangle, ожидает, что установка ширины и высоты будет работать независимо. При подстановке Square это ожидание нарушается, что приводит к некорректным результатам (assert rect.area() == 20 становится ложным, так как для Square площадь будет 16).
Почему это проблема и как исправить:
Нарушение LSP приводит к хрупкости кода, затрудняет его поддержку и расширение, так как подклассы не могут быть безопасно использованы там, где ожидается базовый класс. Правильным решением было бы не наследовать Square от Rectangle, поскольку Square не является 'специальным видом' Rectangle в контексте независимых размеров. Вместо этого можно использовать:
- Общий интерфейс (абстрактный базовый класс): Определить общий интерфейс
Shapeс методомarea(), от которого будут наследоваться какRectangle, так иSquare. - Композиция:
Squareможет содержатьRectangleкак внутренний компонент, управляя его размерами.