Ответ
L — Принцип подстановки Лисков (Liskov Substitution Principle, LSP).
Формулировка: Объекты базового класса должны быть заменяемы объектами его подклассов без изменения корректности программы. Наследник не должен ужесточать предусловия или ослаблять постусловия, определённые базовым классом.
Пример нарушения LSP в PHP:
interface Document {
public function open(): string;
public function save(): void;
}
class ReadOnlyDocument implements Document {
public function open(): string {
return 'Document content';
}
public function save(): void {
// Нарушение LSP! Подкласс не может выполнить контракт базового класса.
throw new RuntimeException('Save is not allowed for read-only documents');
}
}
function processDocument(Document $doc) {
$content = $doc->open();
// Для ReadOnlyDocument вызовет неожиданное исключение
$doc->save();
}
Исправление через пересмотр иерархии:
interface Document {
public function open(): string;
}
interface WritableDocument extends Document {
public function save(): void;
}
class PdfDocument implements WritableDocument {
public function open(): string { /* ... */ }
public function save(): void { /* ... */ }
}
class ReadOnlyReport implements Document {
public function open(): string { /* ... */ }
// Не реализует save(), что корректно
}
function processDocument(Document $doc) {
$content = $doc->open();
// Теперь функция работает с любым Document
}
function saveDocument(WritableDocument $doc) {
$doc->save(); // Гарантировано безопасно
}
На практике LSP обеспечивает: предсказуемость полиморфного поведения, упрощение модульного тестирования (можно использовать моки базового типа) и предотвращение появления в коде проверок типа if ($obj instanceof SpecificClass).
Ответ 18+ 🔞
Давай разжую тебе этот принцип, а то звучит как заклинание из учебника по тёмной магии. Принцип подстановки Лисков — это, по сути, простая, но ёбушки-воробушки важная штука.
Представь, что у тебя есть класс Птица с методом летать(). Ты наследуешь от него класс Пингвин. И тут начинается пиздец. Ты вызываешь $пингвин->летать(), а он, хитрая жопа, либо молча ничего не делает, либо швыряет исключение "Я не летаю, я плаваю!". Вот это и есть нарушение LSP в чистом виде. Программа ожидает, что любая птица летает, а пингвин этот контракт рвёт в клочья. Клиентский код, который работает с Птицей, охуевает и ломается, получив пингвина.
Суть в чём: если уж ты говоришь, что твой класс — это подтип какого-то другого (наследуешься или реализуешь интерфейс), то ты должен делать всё то же самое и даже больше, но не меньше и не другое. Нельзя ослаблять гарантии родителя. Нельзя вместо действия кидать ошибку. Нельзя требовать на вход больше, чем требует родитель. Иначе вся полиморфная магия накрывается медным тазом.
Смотри на твой же пример с документами. Интерфейс Document говорит: "Я умею открываться и сохраняться". А его реализация ReadOnlyDocument заявляет: "Ага, щас, сохраняться — это не про меня, получи исключение". Это пиздопроебибна архитектура. Клиент, который получил просто Document, имеет доверия ебать ноль к тому, что метод save() вообще можно безопасно вызвать.
Исправление — это всегда пересмотр иерархии, разделение ответственности. Как в исправленном примере: есть просто документ для чтения, а есть расширяемый интерфейс для записи. И тогда ReadOnlyReport — честный документ, он не обещает того, чего не умеет. А функция saveDocument() принимает только WritableDocument и спит спокойно, зная, что её не взъебут неожиданным исключением.
На практике соблюдение LSP даёт тебе: предсказуемость, чувак. Ты можешь плодить подклассы, и основной код, работающий с базовым типом, не будет об них спотыкаться. Тестировать легче — заглушку (mock) базового типа подсунул и поехал. И самое главное — исчезает эта мерзкая привычка делать костыли по всему коду: if ($doc instanceof ReadOnlyDocument) { // не сохраняем }. Это признак того, что с иерархией классов у тебя полный хуй с горы.
Запомни: если при замене родителя на потомка у тебя в голове или в коде возникает вопрос "а не сломается ли?", значит, принцип уже в говне. Наследник должен быть полноценной заменой, а не манда с ушами, которая только выглядит похоже.