Ответ
Буква L означает Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP).
Формулировка: Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения корректности этой программы.
Проще говоря: Наследующий класс должен дополнять, а не изменять поведение родительского класса. Код, работающий с базовым классом, должен продолжать корректно работать и с объектами класса-наследника.
Нарушение LSP (пример на Java):
class Rectangle {
protected int width, height;
public void setWidth(int w) { width = w; }
public void setHeight(int h) { height = h; }
public int area() { return width * height; }
}
class Square extends Rectangle {
@Override
public void setWidth(int w) {
super.setWidth(w);
super.setHeight(w); // Нарушение LSP! Изменяет ожидаемое поведение.
}
@Override
public void setHeight(int h) {
super.setHeight(h);
super.setWidth(h); // Нарушение LSP!
}
}
// Код, рассчитывающий на независимую установку ширины и высоты Rectangle, сломается для Square.
Следствие: LSP является основой для корректного использования полиморфизма и проектирования по контракту (предусловия, постусловия, инварианты).
Ответ 18+ 🔞
Ну вот, сидишь ты такой, пишешь свой код, всё вроде красиво, наследование там, полиморфизм... А потом бац — и программа начинает творить какую-то ебанистику, которую и объяснить-то невозможно. А корень зла, сука, часто вот в этой самой букве L прячется, в Принципе подстановки Барбары Лисков.
Что за хуйня? Формулировка звучит умно, но по сути: если у тебя есть кусок кода, который работает с каким-то классом, то он должен так же спокойно, без сюрпризов, работать и с любым его наследником. Подставил объект-потомка — и ничего не должно посыпаться, корректность программы не должна поехать.
А на пальцах: Наследник должен расширять возможности родителя, а не ломать его логику и не менять ожидаемое поведение. Нельзя, чтобы код, который дружил с папой, начинал блевать при встрече с сыном. Это пиздец как важно.
Смотри, как его нарушают (пример на Java):
class Rectangle {
protected int width, height;
public void setWidth(int w) { width = w; }
public void setHeight(int h) { height = h; }
public int area() { return width * height; }
}
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); // И здесь та же хуйня! Нарушение LSP в чистом виде.
}
}
Представь, у тебя есть функция, которая работает с Rectangle. Она устанавливает ширину и высоту по-отдельности, рассчитывая, что это независимые операции. А ты, такой довольный, подсовываешь ей Square. Она ширину поставила — а этот квадратный урод заодно и высоту себе поменял! Всё, логика полетела в тартарары, программа ведёт себя как умалишённая. Код, который рассчитывал на поведение прямоугольника, ломается о квадрат. Вот это и есть нарушение принципа, блядь.
А главная мысль какая? LSP — это, по сути, фундамент для нормального полиморфизма и проектирования по контракту. Если инварианты, предусловия и постусловия родительского класса не сохраняются в потомке, то вся эта красивая архитектура превращается в один большой и страшный говнокод. Так что думай головой, когда наследуешь, а не просто потому что "ой, они вроде похожи".