Ответ
Принцип подстановки Барбары Лисков (LSP) — это один из пяти принципов SOLID. Он гласит, что объекты базового класса должны быть заменяемы объектами его подклассов без нарушения корректности программы. Подкласс должен расширять, а не изменять или сужать поведение родительского класса.
Почему это важно? Нарушение LSP приводит к хрупкому коду, где замена типа вызывает неожиданные ошибки, и ломает полиморфизм.
Пример нарушения LSP:
// Базовый класс
class Rectangle {
protected int width;
protected int height;
public void setWidth(int w) { this.width = w; }
public void setHeight(int h) { this.height = h; }
public int getArea() { return width * height; }
}
// Подкласс, нарушающий LSP
class Square extends Rectangle {
@Override
public void setWidth(int w) {
super.setWidth(w);
super.setHeight(w); // Побочный эффект: меняет высоту
}
@Override
public void setHeight(int h) {
super.setHeight(h);
super.setWidth(h); // Побочный эффект: меняет ширину
}
}
// Клиентский код, который сломается
public void resizeRectangle(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
assert r.getArea() == 20; // Утверждение провалится для Square
}
Решение: Не наследовать Square от Rectangle. Вместо этого использовать композицию, общий интерфейс или признак (flag), если квадрат — частный случай прямоугольника.
Ответ 18+ 🔞
Давай разберем эту хитрую концепцию, которая с виду проще пареной репы, а на деле — сплошные подводные ебучки.
Вот представь, есть у тебя класс Прямоугольник. Ну, прямоугольник и прямоугольник, ширина, высота, площадь. Всё логично, хуле. И тут тебе в голову приходит гениальная, блядь, мысль: «А квадрат-то — это частный случай прямоугольника! Наследую-ка я класс Квадрат от Прямоугольника!». И вроде бы всё по науке, ёпта.
А теперь смотри, какую дичь мы получаем. Клиентский код, который работает с прямоугольниками, ожидает вполне определённого поведения: поставил ширину — ширина поменялась, поставил высоту — высота поменялась. И площадь, соответственно, пересчиталась.
public void resizeRectangle(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
assert r.getArea() == 20; // Ожидаем 5 * 4 = 20
}
А теперь подсовываем этому коду наш «квадрат», который, по нашей «гениальной» логике, является подклассом прямоугольника. И что происходит? А происходит пиздец, вот что.
// Подкласс, нарушающий всё на свете
class Square extends Rectangle {
@Override
public void setWidth(int w) {
super.setWidth(w);
super.setHeight(w); // АХТУНГ! Меняем и высоту тоже!
}
@Override
public void setHeight(int h) {
super.setHeight(h);
super.setWidth(h); // И тут, нахуй, ширину подменяем!
}
}
Клиентский код вызвал setWidth(5), а квадрат, сука, заодно и высоту в 5 поставил. Потом клиент вызывает setHeight(4), а квадрат и ширину в 4 меняет. В итоге у нас квадрат 4x4, площадь 16, а утверждение assert r.getArea() == 20 летит в пизду вместе с доверием к нашей архитектуре. Вот это и есть нарушение принципа подстановки Лисков.
Суть принципа, если на пальцах: если ты где-то в коде используешь Прямоугольник, то ты должен иметь возможность подсунуть туда любой его подкласс (например, Квадрат), и программа не должна обосраться. Она должна работать так, как будто ничего не произошло. А у нас — произошло. И сильно.
Как не наступать на эти грабли? Да очень просто — не делать Квадрат наследником Прямоугольника. Это классический пример, который в учебниках приводят, чтобы показать, что «is-a» отношение (квадрат IS-A прямоугольник) в реальном ООП — часто хуйня полная. Лучше использовать композицию, общий интерфейс Фигура с методом getArea(), или вообще отдельные классы без наследования.
Иначе получается, что подкласс не расширяет, а изменяет поведение родителя. А это, как говорится, чих-пых тебя в сраку, прямой путь к багам, которые ищешь потом полгода.