Правило подстановки Лисков в объектно-ориентированном программировании (ООП)

Правило подстановки Лисков в объектно-ориентированном программировании (ООП)
Правило подстановки Лисков в объектно-ориентированном программировании (ООП) - sonderquest @ Unsplash

Понимание правила истории принципа подстановки Лисков и того, как оно запрещает изменение состояния подтипов, которое не допускается в супертипах

Принцип подстановки Лисков (Liskov substitution principle, LSP) является одним из фундаментальных принципов объектно-ориентированного программирования. Он определяет, что объекты должны быть заменяемыми своими подтипами, то есть код, использующий супертип, должен работать корректно со всеми его подтипами без необходимости внесения изменений.

Однако, помимо обычных правил этого принципа, также существует правило истории (history rule), которое накладывает дополнительные ограничения на изменение состояния подтипов. Изменение состояния в подтипе не должно быть разрешено, если это изменение не допускается в супертипе.

Для лучшего понимания данного принципа, давайте рассмотрим пример:

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

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

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

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

class Square extends Rectangle {
  public setWidth(width: number) {
    this.width = width;
    this.height = width;
  }

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

В данном примере у нас есть класс Rectangle (Прямоугольник) с методами для установки ширины и высоты, а также для получения площади прямоугольника. Затем у нас есть подтип Square (Квадрат), который наследуется от класса Rectangle.

Однако, следуя правилам принципа подстановки Лисков, подтип не может изменять поведение методов своего супертипа так, чтобы оно стало некорректным для использования супертипом. В данном примере, методы setWidth() и setHeight() были переопределены в классе Square, но они полностью изменяют поведение предкового класса.

Например, если мы создадим экземпляр класса Rectangle и вызовем установку ширины и высоты, затем вызовем метод getArea(), результат будет корректным. Однако, если мы сделаем то же самое с экземпляром класса Square, результат будет некорректным.

Это нарушает принцип подстановки Лисков, так как код, использующий супертип Rectangle, должен быть применим к подтипу Square, но в данном случае он работает некорректно.

Вместо этого, правильным подходом будет использование композиции или агрегации объектов. Например, мы можем создать отдельный класс для Square, который содержит ссылку на экземпляр класса Rectangle и делегирует вызовы методов ему.

class Square {
  private rectangle: Rectangle;

  constructor(sideLength: number) {
    this.rectangle = new Rectangle();
    this.rectangle.setWidth(sideLength);
    this.rectangle.setHeight(sideLength);
  }

  public getArea() {
    return this.rectangle.getArea();
  }
}

Теперь, используя этот подход, мы можем создать экземпляр класса Square и вызвать метод getArea(), и результат будет корректным.

В заключение, правило истории принципа подстановки Лисков является важным аспектом правильного проектирования объектно-ориентированного кода. Оно запрещает состояния в подтипах, которые не допускаются в супертипах, поэтому следует быть внимательным при изменении поведения подтипов, чтобы не нарушить этот принцип.


LetsCodeIt, 14 августа 2023 г., 08:04

Похожие посты

Принципы объектно-ориентированного дизайна и валидация ходов в онлайн-шахматахСтатический метод сервиса или зависимости: преимущества, недостатки и рекомендацииОграничения дизайна программы: использование MainApp как посредника для компонентов и альтернативные подходыОбновление предупреждающих операторов в подклассах: новый метод с списком предупрежденийРефакторинг большого метода с вложенными операторами switch в JavaРеализация полиморфизма в Java с помощью примера класса Person и MarriedPersonНаследование без полиморфизма/переопределения - правильная практика?Принцип подстановки Лисков и наследование: Исследование взаимоотношенийКомпозиция превыше наследования: проблемы и решения в C#Понимание принципа открытости-закрытости в разработке кода и роль инкапсуляции. Как избежать дублирования кода и использовать наследование для эффективного проектирования классов