← Назад к вопросам

Что означает буква L в SOLID?

2.0 Middle🔥 221 комментариев
#Архитектура и паттерны#ООП

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP)

Буква L в SOLID обозначает Принцип подстановки Лисков (Liskov Substitution Principle, LSP). Это фундаментальный принцип объектно-ориентированного проектирования, сформулированный Барбарой Лисков в 1987 году. На интуитивном уровне он гласит: «Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения корректности программы».

Суть принципа

Если у нас есть класс Родитель и унаследованный от него класс Потомок, то мы должны иметь возможность использовать объект класса Потомок везде, где ожидается объект класса Родитель, и программа при этом должна продолжать работать корректно. Это не просто о синтаксической совместимости (например, одинаковой сигнатуре методов), а о семантической корректности.

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

Пример нарушения LSP

Рассмотрим классический пример с геометрическими фигурами:

// Базовый класс
class Rectangle {
    protected $width;
    protected $height;

    public function setWidth($width) {
        $this->width = $width;
    }

    public function setHeight($height) {
        $this->height = $height;
    }

    public function getArea() {
        return $this->width * $this->height;
    }
}

// Подкласс, нарушающий LSP
class Square extends Rectangle {
    public function setWidth($width) {
        $this->width = $width;
        $this->height = $width; // Нарушение: меняется и высота!
    }

    public function setHeight($height) {
        $this->height = $height;
        $this->width = $height; // Нарушение: меняется и ширина!
    }
}

Проблема: Клиентский код, работающий с Rectangle, ожидает, что setWidth() и setHeight() изменяют только соответствующее измерение. Однако Square нарушает это ожидание, изменяя оба измерения одновременно. Это приводит к ошибкам:

function calculateArea(Rectangle $rectangle) {
    $rectangle->setWidth(5);
    $rectangle->setHeight(4);
    // Ожидаемая площадь: 5 * 4 = 20
    return $rectangle->getArea();
}

$square = new Square();
echo calculateArea($square); // Вернет 16, а не 20! Нарушение LSP.

Как соблюдать LSP?

  1. Строгое соблюдение контрактов: Подклассы должны выполнять все предусловия (требования к входным данным) и постусловия (гарантии на выходе) базового класса, а также не должны нарушать инвариантов (условий, истинных на протяжении всей жизни объекта).
  2. Избегание ужесточения предусловий: Подкласс не должен требовать больше, чем базовый класс. Например, если метод базового класса принимает целое число, подкласс не должен требовать только положительные числа.
  3. Избегание ослабления постусловий: Подкласс не должен гарантировать меньше, чем базовый класс. Например, если базовый метод гарантирует, что возвращаемый массив не будет пустым, подкласс тоже должен это гарантировать.
  4. Сохранение всех побочных эффектов: Подкласс должен сохранять все побочные эффекты методов базового класса.
  5. Предпочтение композиции наследованию: Если отношения «является» (is-a) не идеальны, часто лучше использовать композицию или интерфейсы.

Исправленный дизайн (соблюдая LSP)

// Абстракция "Фигура"
interface Shape {
    public function getArea();
}

// Конкретные реализации, не связанные наследованием
class Rectangle implements Shape {
    private $width;
    private $height;

    public function __construct($width, $height) {
        $this->width = $width;
        $this->height = $height;
    }

    public function getArea() {
        return $this->width * $this->height;
    }
}

class Square implements Shape {
    private $side;

    public function __construct($side) {
        $this->side = $side;
    }

    public function getArea() {
        return $this->side * $this->side;
    }
}

// Клиентский код работает корректно с любой Shape
function printArea(Shape $shape) {
    echo "Area: " . $shape->getArea() . "\n";
}

printArea(new Rectangle(5, 4)); // 20
printArea(new Square(5));       // 25

Почему LSP важен для PHP-разработчика?

  • Повышает надежность: Код становится предсказуемым, уменьшается количество скрытых ошибок.
  • Упрощает тестирование: Моки и стабы можно легко подставлять благодаря гарантиям контрактов.
  • Улучшает сопровождаемость: Позволяет безопасно расширять систему, добавляя новые подклассы, не ломая существующий клиентский код.
  • Позволяет использовать полиморфизм в полную силу: Без LSP полиморфизм становится опасным и может привести к неожиданному поведению.

Таким образом, LSP — это не просто правило о наследовании, а глубокий принцип проектирования, который обеспечивает семантическую согласованность иерархий классов и делает систему устойчивой к расширению. Нарушение LSP часто является признаком ошибочной модели предметной области, где отношение «является» (is-a) подменяется отношением «содержит» (has-a) или другим. Соблюдение этого принципа — признак зрелого объектно-ориентированного дизайна.