Ответ
Главная проблема — нарушение Принципа подстановки Лисков (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 нарушен. В таких случаях наследование является неверным выбором, и стоит рассмотреть композицию или другую архитектуру.
Ответ 18+ 🔞
А, слушай, смотри, тут такая дичь, блядь, классическая! Все думают: ну квадрат — он же прямоугольник, да? Ширина, высота, всё как у людей. Берём и наследуем, хуле. А потом — пиздец, программа летит в тартарары, и все сидят, чешут репу: "Почему?"
А причина-то, блядь, в нарушении Принципа подстановки Лисков (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 # Опа-на! Меняю ширину — заодно и высоту нахуярил!
def set_height(self, height):
self._width = height
self._height = height # И тут то же самое! Высоту поменял — и ширину за собой потянул!
Вроде бы, ну и что? Квадрат же, у него стороны равны, я инвариант сохраняю, молодец я, блядь. Ан нет! Смотри, что происходит дальше.
Допустим, есть у нас функция, которая работает с прямоугольником. Она ожидает, что ширина и высота — независимые хуйни, и меняет их по отдельности:
def use_rectangle(rect: Rectangle):
rect.set_width(20)
rect.set_height(10)
# Ожидаем площадь 200, блядь! 20 умножить на 10.
# А получаем... А получаем пиздец!
assert rect.area() == 200, "Behavior is broken!"
r = Rectangle(5, 5)
use_rectangle(r) # Всё ок, работает, спасибо, до свидания.
s = Square(5)
use_rectangle(s) # А тут — бабах! AssertionError! Программа в ахуе!
Почему? Да потому что наш "квадратик", сука, когда ему меняют высоту, он ещё и ширину под себя подминает! И наоборот. В итоге площадь получается не 200, а 100, и вся логика летит в пизду. Это и есть нарушение LSP в чистом виде: квадрат не может заменить прямоугольник, не сломав ожидаемого поведения.
Вывод, блядь, простой, как три копейки: не всякая "is-a" связь (квадрат — это прямоугольник) должна превращаться в наследование в коде. Иногда лучше взять композицию, чтобы не выстрелить себе в ногу, а потом орать "ой, бля, как же так вышло?". Вот так вот, ебать мои старые костыли, принципы SOLID — они не просто так придуманы, чтобы мозги нам пудрить.