Python
Senior
**Проблема прямоугольника** — это классический пример нарушения принципов ООП,
Ответы
**Проблема прямоугольника** — это классический пример нарушения принципов ООП, а именно **принципа подстановки Лисков (LSP)**.
Её ещё называют **Rectangle–Square Problem**.
---
## Суть проблемы (кратко)
Интуитивно кажется логичным сказать:
> **Квадрат — это прямоугольник**
Но в **ООП это приводит к логической ошибке**, потому что квадрат **ужесточает поведение** прямоугольника и ломает ожидания кода.
---


---
## Классический пример
### Базовый класс: Прямоугольник
```python
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
def set_width(self, w):
self._width = w
def set_height(self, h):
self._height = h
def area(self):
return self._width * self._height
```
---
### Наследник: Квадрат (❌ ошибка дизайна)
```python
class Square(Rectangle):
def set_width(self, w):
self._width = w
self._height = w
def set_height(self, h):
self._width = h
self._height = h
```
---
## Где возникает проблема
Код, который **корректно работает с Rectangle**:
```python
def resize(rect: Rectangle):
rect.set_width(5)
rect.set_height(10)
return rect.area()
```
### Ожидание:
```text
5 × 10 = 50
```
### Реальность:
```python
resize(Rectangle(2, 3)) # 50 ✅
resize(Square(2, 2)) # 100 ❌
```
👉 Подстановка `Square` **меняет поведение**
👉 Контракт `Rectangle` **нарушен**
---
## Почему это нарушение LSP
**Принцип Лисков (LSP):**
> Объекты дочернего класса должны быть взаимозаменяемы с объектами базового класса **без изменения корректности программы**
В нашем случае:
* `Rectangle` допускает **независимое изменение ширины и высоты**
* `Square` **запрещает это**
❌ Значит `Square` **НЕ является корректным подтипом** `Rectangle` в ООП-смысле
---
## Ключевая мысль (очень важная)
> **Математическое “is-a” ≠ ООП “is-a”**
* В геометрии: квадрат ⊂ прямоугольник ✅
* В ООП: квадрат **не подходит** под контракт прямоугольника ❌
---
## Правильные решения
### ✅ Вариант 1: Не использовать наследование
```python
class Shape:
def area(self):
pass
class Rectangle(Shape):
...
class Square(Shape):
...
```
✔ Нет нарушения
✔ Явные контракты
✔ Без сюрпризов
---
### ✅ Вариант 2: Сделать объект неизменяемым
```python
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
```
* Нет `set_width` / `set_height`
* Нет нарушения контракта
---
### ❌ Вариант 3: «Костыли» (плохо)
```python
def set_width(self, w):
if isinstance(self, Square):
raise Exception()
```
❌ Ломает полиморфизм
❌ Антипаттерн
---
## Почему этот пример так любят на собеседованиях
Он проверяет, понимаешь ли ты:
* разницу между **наследованием и моделированием**
* что такое **контракт класса**
* что **ООП — это не про иерархии, а про поведение**
* принцип **composition over inheritance**
---
## Короткая шпаргалка
* Проблема прямоугольника = нарушение **LSP**
* Наследник **не должен усиливать ограничения**
* Если поведение меняется → это **не is-a**
* Сомневаешься → **композиция лучше наследования**
---
Если хочешь, дальше могу:
* разобрать **аналогичные ловушки (Bird → Penguin)**
* связать пример с **SOLID на реальных сервисах**
* показать, как **Python duck typing** частично обходит проблему
* разобрать **как объяснять это на собеседовании за 30 секунд**
Скажи, что полезнее 👍