Что такое принцип подстановки Лисков (LSP)?

«Что такое принцип подстановки Лисков (LSP)?» — вопрос из категории Архитектура, который задают на 35% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Принцип подстановки Лисков (LSP) — третий принцип SOLID. Он гласит: объекты в программе должны быть заменяемы экземплярами их подтипов (наследников) без изменения корректности этой программы.

Проще говоря: Если у вас есть функция, работающая с классом Родитель, то она должна безошибочно работать и с любым классом Ребёнок, унаследованным от Родителя. Наследник не должен нарушать контракт базового класса.

Ключевые аспекты контракта:

  1. Предусловия (требования к входным данным) не могут быть усилены в подклассе.
  2. Постусловия (гарантии на выходе) не могут быть ослаблены в подклассе.
  3. Инварианты (условия, истинные на протяжении всей жизни объекта) должны сохраняться.

Классический пример нарушения LSP:

// Базовый класс, предполагающий, что все птицы летают.
class Bird {
    public function fly(): int {
        return $this->airSpeedVelocity; // Возвращает скорость полёта
    }
}

// Подкласс, нарушающий контракт. Пингвин не летает.
class Penguin extends Bird {
    public function fly(): int {
        throw new RuntimeException('Penguins cannot fly!'); // Усилено предусловие (теперь нужно НЕ летать)
        // или return 0; // Ослаблено постусловие (скорость полёта не имеет смысла)
    }
}

// Функция, рассчитанная на работу с Bird.
function traverseForest(array $birds) {
    foreach ($birds as $bird) {
        $speed = $bird->fly(); // Для Penguin выбросится исключение, программа сломается.
        // ...
    }
}

Как исправить, следуя LSP: Нужно пересмотреть иерархию наследования, чтобы она отражала реальные возможности объектов.

// Более точная абстракция.
abstract class Bird {
    // Общие для всех птиц методы (есть, пить, размножаться)
}

// Интерфейс для способности, а не для сущности.
interface Flyable {
    public function fly(): int;
}

class Sparrow extends Bird implements Flyable {
    public function fly(): int {
        return 15; // OK
    }
}

class Penguin extends Bird {
    public function swim(): int {
        return 10; // OK, пингвин плавает
    }
}

// Теперь функция работает только с летающими объектами.
function traverseForest(array $flyableBirds) {
    foreach ($flyableBirds as $bird) {
        $speed = $bird->fly(); // Гарантированно безопасно
    }
}

Соблюдение LSP делает систему устойчивой к изменениям и позволяет безопасно использовать полиморфизм.