Если вы еще этого не сделали, ознакомьтесь с моей предыдущей статьей Открыто-закрыто.

Принцип замены Лискова:

Напишите родительский класс или интерфейсы таким образом, чтобы ВСЕ его производные классы имели действительные реализации для ВСЕХ методов родительского класса.

формально подтипы должны быть взаимозаменяемыми для своих базовых типов.

Давайте разберемся на примере прямоугольника и квадрата,

Поскольку математически квадрат является прямоугольником, вы можете использовать отношение IS-A и методы переопределения,

но это нарушает LSP, поскольку методы в дочернем классе не могут быть полностью заменены методами родительского класса.

Допустим, клиент реализует метод, который принимает прямоугольник и устанавливает высоту и ширину, и по какой-то странной причине пытается проверить область.

Если мы передаем этому методу объект Square, но поскольку метод setWidth() также изменяет высоту, площадь будет 20*20=400, так что даже математически квадратная представляет собой прямоугольник, с точки зрения поведения это не так, а отношения IS-A основаны на поведении.

То же самое, но все же другое, не так ли?

Если вы заметили, здесь две проблемы

  1. Здесь автор setAndCheckArea() предположил, что установка ширины не меняет высоту, и это разумно предположить, потому что просто взглянув на класс Rectangle, можно понять, в конце концов, это два разных метода для двух для разных целей, поэтому Square на самом деле не заменяет Rectangle здесь, следовательно, это нарушает LSP.
  2. Мы также можем обвинить автора класса Square, так как реализации методов имеют некоторые запахи кода, методы в классе Square делают что-то большее, чем то, что указано в их названии, но опять же реализация правильно, так как код будет работать нормально, если он протестирован для всех прямоугольников, которые являются квадратами.

Итак, что мы можем здесь сделать?

  1. Определите предварительные условия, которым должен удовлетворять входной объект перед выполнением метода, и определите постусловия, которым должно удовлетворять конечное состояние объекта. Мы можем сделать это в комментариях или как часть метода в родительском классе. Это поможет клиентам понять контракт конкретного класса/интерфейса/функции при его использовании, и, следовательно, авторы производных классов или клиентского кода не будут делать неверных предположений.

2. Напишите модульные тесты для таких конкретных сценариев, которые могут помочь автору клиентского кода понять предположения.

Еще несколько моментов, чтобы избежать нарушения LSP:

  1. Пересмотрите свои дочерние классы, которые удаляют/уменьшают функциональность базовых классов, они более подвержены будущим ошибкам, поскольку дочерние элементы, которые делают меньше, чем их основа, обычно не могут быть заменены этой базой и, следовательно, нарушают LSP.
  2. Избегайте добавления большего количества исключений в производные классы, чем в базовые классы, поскольку пользователи базового класса не ожидают, что они не заменимы.
  3. Следование LSP включает OCP. Наличие производных классов/интерфейсов в качестве идеальной замены базовых классов/интерфейсов делает их расширяемыми без модификации и, следовательно, позволяет OCP.
  4. Нарушение LSP является латентным нарушением OCP.

это все для LSP, давайте посмотрим на принцип разделения интерфейса.