Рассмотрите Java-код: class Rectangle { int width,height; void setWidth(int w){width=w;} void setHeight(int h){height=h;} } class Square extends Rectangle { @Override void setWidth(int w){width=height=w;} @Override void setHeight(int h){width=height=h;} } — почему этот дизайн нарушает принцип подстановки Лисков и как спроектировать классы иначе?

19 Мар в 12:09
11 +1
0
Ответы
1
Проблема (кратко): подстановка Лисков (LSP) требует, чтобы объект подкласса мог заменять базовый объект без изменения желаемого поведения клиента. В вашем коде класс Square меняет семантику сеттеров Rectangle: у Rectangle ожидание — ширина и высота независимы, у Square вводится дополнительное инвариантное условие width=heightwidth = heightwidth=height. Это меняет постусловия и побочные эффекты методов, нарушая LSP.
Пример нарушения: клиентский код
- создаёт `Rectangle r = new Square();`
- выполняет `r.setWidth(5); r.setHeight(10);`
Ожидание для Rectangle: в конце width=5width = 5width=5 и height=10height = 10height=10. Для Square результат — width=height=10width = height = 10width=height=10. Поведение изменилось — подстановка некорректна.
Почему именно нарушается LSP: подкласс
- усиливает инвариант (вводит новое требование width=heightwidth = heightwidth=height),
- изменяет эффект и контракт методов (сеттеры перестают быть независимыми),
а LSP запрещает укреплять предусловия и изменять гарантии/побочные эффекты так, чтобы клиент, работавший с базовым классом, оказался сломан.
Как спроектировать иначе (варианты):
1) Разделить типы (не наследовать Square от Rectangle)
- Сделать Rectangle и Square отдельными классами (они могут реализовывать общий интерфейс только для чтения, напр.: `interface Shape { int getWidth(); int getHeight(); int getArea(); }`).
- Делать объекты неизменяемыми: `Rectangle(int w,int h)`, `Square(int side)` без сеттеров. Тогда нет конфликтов поведений.
2) Композиция вместо наследования
- `class Square { private final Rectangle rect; Square(int s){ rect = new Rectangle(s,s); } ... }`
- Square управляет внутренним Rectangle, но не наследует его изменяемые API, поэтому не ломает контракт.
3) Сделать базовый класс более абстрактным или финальным
- Если нужен общий mutable API, вынести общие операции в абстракцию, где поведение однозначно согласовано, или пометить `Rectangle` как `final`, чтобы не допускать наследования, которое может нарушить инварианты.
4) Явные методы для согласованных изменений
- Если нужен мутируемый Square, запретить независимые сеттеры в интерфейсе (только `setSize(int s)` для квадрата) и для прямоугольника — `setSize(int w,int h)`; общий интерфейс не должен содержать методов, которые для одного типа имеют иные смысловые гарантии.
Резюме: не делайте Square подклассом Rectangle, если у него другой семантический контракт (инварианты/поведения). Лучшее решение — отдельные классы с общим интерфейсом для чтения или композиция; если наследование необходимо — гарантируйте, что подкласс сохраняет все инварианты и контракты базового класса.
19 Мар в 12:53
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир