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

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

1.0 Junior🔥 211 комментариев
#JavaScript Core

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

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

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

Ответ: Принцип подстановки Барбары Лисков (LSP)

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

Объекты в программе должны быть заменяемыми экземплярами их базовых типов без изменения корректности этой программы.

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

Суть принципа и почему он важен

Нарушение LSP — частая причина хрупкости архитектуры. Принцип глубже, чем просто «наследование должно работать». Он требует, чтобы подкласс не изменял ожидаемого поведения базового класса. Это касается:

  • Сигнатур методов (типы параметров, возвращаемые значения).
  • Инвариантов (внутренних условий, которые всегда истинны для класса).
  • Постусловий (условий, истинных после выполнения метода).
  • Предусловий (условий, истинных перед выполнением метода). Подкласс не должен ужесточать предусловия или ослаблять постусловия.

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

Рассмотрим известный пример с прямоугольником и квадратом. С математической точки зрения квадрат — это частный случай прямоугольника. Но в коде это наследование приводит к проблеме.

// Базовый класс Прямоугольник
class Rectangle {
    protected width: number = 0;
    protected height: number = 0;

    setWidth(width: number): void {
        this.width = width;
    }
    setHeight(height: number): void {
        this.height = height;
    }
    getArea(): number {
        return this.width * this.height;
    }
}

// Класс Квадрат, нарушающий LSP
class Square extends Rectangle {
    // Квадрат меняет поведение: изменение ширины всегда меняет и высоту
    setWidth(width: number): void {
        this.width = width;
        this.height = width; // Нарушение инварианта родителя!
    }
    setHeight(height: number): void {
        this.height = height;
        this.width = height; // Нарушение инварианта родителя!
    }
}

// Функция, работающая с базовым классом
function calculateArea(rectangle: Rectangle): number {
    rectangle.setWidth(5);
    rectangle.setHeight(4);
    // Ожидаемая площадь: 5 * 4 = 20
    return rectangle.getArea();
}

const rect = new Rectangle();
console.log(calculateArea(rect)); // 20 - корректно

const square = new Square();
console.log(calculateArea(square)); // 16! Нарушение LSP. Функция дала сбой.

Здесь Square нарушает контракт класса Rectangle. Методы setWidth и setHeight в прямоугольнике подразумевают независимое изменение сторон. Квадрат ломает это ожидание. Как следствие, любая функция, рассчитанная на работу с Rectangle, может дать неверный результат для Square.

Практические следствия и применение в Frontend

Во фронтенд-разработке LSP помогает создавать устойчивые, переиспользуемые компоненты и системы:

  1. Компонентный подход (React/Vue): Компонент-потомок должен полностью реализовывать API (props, events, slots) родительского компонента или абстракции. Замена базового компонента на дочерний не должна ломать родительские компоненты или страницы.
  2. Система типов (TypeScript): LSP — основа полиморфизма и ковариантности/контравариантности типов в TypeScript. Корректное использование интерфейсов (interface) и абстрактных классов гарантирует, что реализации будут взаимозаменяемы.
  3. API и состояние: Если разные сущности (например, GuestUser и AuthorizedUser) реализуют общий интерфейс User, код, работающий с User, не должен проверять конкретный тип и «подстраиваться» под него.
  4. Тестирование: Принцип позволяет легко использовать моки (mocks) и стабы (stubs) в тестах, так как они следуют тому же контракту, что и реальные объекты.

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

  • Предпочитайте композицию наследованию («has-a» вместо «is-a»). Часто проблема решается созданием общего интерфейса, который реализуют оба класса.
  • Строго соблюдайте контракты интерфейсов. Дочерний класс может расширять функциональность, но не должен изменять базовую.
  • Избегайте проверок типа в коде клиента. Если вам приходится писать if (obj instanceof Square), это явный признак нарушения LSP.

Вывод

Принцип подстановки Лисков — это не техническое правило, а философия проектирования. Он обеспечивает устойчивость архитектуры, позволяя системе развиваться через добавление новых классов без модификации существующего, работающего кода. Нарушение LSP ведет к хрупким иерархиям, ошибкам и сложностям в поддержке, особенно в крупных frontend-приложениях с множеством взаимосвязанных компонентов и модулей. Следование LSP делает код предсказуемым, тестируемым и готовым к масштабированию.

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