Проблема (кратко): подстановка Лисков (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, если у него другой семантический контракт (инварианты/поведения). Лучшее решение — отдельные классы с общим интерфейсом для чтения или композиция; если наследование необходимо — гарантируйте, что подкласс сохраняет все инварианты и контракты базового класса.
Пример нарушения: клиентский код
- создаёт `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, если у него другой семантический контракт (инварианты/поведения). Лучшее решение — отдельные классы с общим интерфейсом для чтения или композиция; если наследование необходимо — гарантируйте, что подкласс сохраняет все инварианты и контракты базового класса.