Ответ
Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP) — это третий из пяти принципов SOLID. Он гласит:
Объекты базового класса должны быть заменяемыми объектами его подклассов без изменения корректности программы.
Это означает, что наследующий класс должен:
- Полностью реализовывать контракт (интерфейс) родительского класса.
- Не ужесточать предусловия (требования к входным данным).
- Не ослаблять постусловия (гарантии на результат и состояние).
- Не изменять инварианты класса (условия, всегда истинные для объекта).
- Не выбрасывать новых исключений, не являющихся подтипами исключений базового метода.
Классический пример нарушения LSP (Квадрат/Прямоугольник):
// Базовый класс
class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
// Подкласс, нарушающий LSP
class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // Побочный эффект: меняет и высоту!
}
@Override
public void setHeight(int height) {
this.height = height;
this.width = height; // Побочный эффект: меняет и ширину!
}
}
// Клиентский код, рассчитанный на работу с Rectangle
public void resizeRectangle(Rectangle r) {
// Предполагается, что ширина и высота меняются независимо
r.setWidth(5);
r.setHeight(4);
assert r.getArea() == 20; // Утверждение будет ЛОЖНЫМ для Square!
// Для Square после setHeight(4) ширина тоже станет 4, и площадь будет 16.
}
Почему это нарушение? Клиентский код resizeRectangle ожидает, что setWidth и setHeight работают независимо, как определено в Rectangle. Класс Square нарушает это поведение, внося скрытые побочные эффекты. Замена Rectangle на Square приводит к ошибке.
Следствие LSP: Наследование должно описывать отношение «является» (is-a) не только с точки зрения данных, но и с точки зрения поведения. Если подкласс не может выполнять все обязанности базового класса, не внося изменений в ожидаемое поведение, наследование применять не следует. Часто проблему решает использование композиции или выделение общего интерфейса.