Ответ
Главная проблема — нарушение Принципа подстановки Лисков (Liskov Substitution Principle, LSP). Этот принцип гласит, что объекты дочерних классов должны быть способны заменять объекты родительского класса без изменения корректности программы.
Нарушение LSP приводит к непредсказуемому поведению, усложняет поддержку и создаёт хрупкую иерархию классов.
Классический пример: Проблема "Квадрат-Прямоугольник"
Логично предположить, что Квадрат является частным случаем Прямоугольника и может от него наследоваться. Однако это нарушает LSP.
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
def get_width(self):
return self._width
def set_width(self, width):
self._width = width
def get_height(self):
return self._height
def set_height(self, height):
self._height = height
def area(self):
return self._width * self._height
class Square(Rectangle):
def __init__(self, size):
super().__init__(size, size)
# Переопределяем сеттеры, чтобы сохранить инвариант квадрата (ширина == высота)
def set_width(self, width):
self._width = width
self._height = width # Нарушение LSP: изменение ширины неожиданно меняет и высоту
def set_height(self, height):
self._width = height
self._height = height # Нарушение LSP: изменение высоты неожиданно меняет и ширину
Почему это проблема?
Рассмотрим функцию, которая работает с Rectangle и ожидает, что изменение ширины не затронет высоту:
def use_rectangle(rect: Rectangle):
rect.set_width(20)
rect.set_height(10)
# Ожидаем площадь 200 (20 * 10)
# Но для Square площадь будет 100 (10 * 10), т.к. set_height изменил и ширину
assert rect.area() == 200, "Behavior is broken!"
r = Rectangle(5, 5)
use_rectangle(r) # OK
s = Square(5)
use_rectangle(s) # AssertionError: Behavior is broken!
Поскольку Square не может быть использован вместо Rectangle без изменения логики, принцип LSP нарушен. В таких случаях наследование является неверным выбором, и стоит рассмотреть композицию или другую архитектуру.