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

Какие знаешь принципы Барбары Лисков?

2.3 Middle🔥 122 комментариев
#JavaScript Core

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

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

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

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

Принцип подстановки Барбары Лисков — это третий из пяти принципов SOLID в объектно-ориентированном программировании. Он был сформулирован Барбарой Лисков в 1987 году и гласит:

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

Проще говоря: если класс S является подклассом класса T, то объекты типа T можно заменять объектами типа S без нарушения работы системы. Это гарантирует, что наследование используется корректно, поддерживая логическую согласованность.


Ключевые аспекты принципа

  1. Контракты методов:

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

    • Наследование должно отражать отношение «является» (is-a) не только синтаксически, но и поведенчески.
    • Классический пример нарушения: квадрат, наследующий от прямоугольника. Метод setWidth у квадрата меняет и высоту, что неожиданно для кода, работающего с прямоугольниками.

Пример нарушения и соблюдения LSP

❌ Нарушение принципа:

class Rectangle {
  protected width: number;
  protected height: number;

  setWidth(width: number) {
    this.width = width;
  }

  setHeight(height: number) {
    this.height = height;
  }

  getArea(): number {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width: number) {
    this.width = width;
    this.height = width; // Неожиданное изменение поведения!
  }

  setHeight(height: number) {
    this.height = height;
    this.width = height; // Нарушает контракт Rectangle!
  }
}

function testArea(shape: Rectangle) {
  shape.setWidth(5);
  shape.setHeight(4);
  console.log(shape.getArea()); // Ожидается 20, но для Square будет 16
}

const rect = new Rectangle();
testArea(rect); // 20

const square = new Square();
testArea(square); // 16 — ошибка!

Здесь Square нарушает LSP: клиентский код testArea ожидает, что setWidth и setHeight работают независимо, но подкласс меняет это поведение.

✅ Соблюдение принципа:

abstract class Shape {
  abstract getArea(): number;
}

class Rectangle extends Shape {
  constructor(private width: number, private height: number) {
    super();
  }

  getArea(): number {
    return this.width * this.height;
  }
}

class Square extends Shape {
  constructor(private side: number) {
    super();
  }

  getArea(): number {
    return this.side ** 2;
  }
}

function printArea(shape: Shape) {
  console.log(shape.getArea()); // Работает с любым Shape
}

const shapes: Shape[] = [new Rectangle(5, 4), new Square(4)];
shapes.forEach(printArea); // 20, 16 — корректно!

Практические следствия LSP для Frontend

  1. Компонентный подход:

    • В React/Vue/Angular базовые компоненты должны быть заменяемыми производными. Например, BaseButton и IconButton: если IconButton ломает обработчик клика — это нарушение LSP.
  2. Типизация TypeScript:

    interface DataFetcher {
      fetch(): Promise<Data>;
    }
    
    class UserFetcher implements DataFetcher {
      async fetch(): Promise<UserData> { // Ковариантность возвращаемого типа допустима
        return { id: 1, name: 'John' };
      }
    }
    

    Возвращаемый тип может быть подтипом (ковариантность), но параметры методов должны быть супертипами (контравариантность) или точно такими же.

  3. Инварианты состояния:

    • Подкласс не должен изменять инварианты базового класса. Например, если базовый класс гарантирует, что status всегда 'idle' | 'loading', подкласс не должен добавлять 'cancelled'.
  4. Исключения:

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

Почему LSP важен в современной разработке?

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

Вывод

LSP — это не просто правило о синтаксической замене, а требование семантической эквивалентности. Его нарушение приводит к хрупким системам, где изменение в подклассе вызывает каскадные ошибки. Для Frontend Developer понимание LSP критично при проектировании компонентов, состоянии приложения (например, в Redux) и абстракциях над API. Соблюдение принципа делает код расширяемым и устойчивым к изменениям.