Как этот пример нарушает LSP, что затем приводит к нарушению OCP?

Из Agile Principles, Patterns, and Practices in C# Роберта Мартина,

Листинг 10-1. Нарушение LSP, вызывающее нарушение OCP

struct    Point   {double x,  y;}
public    enum    ShapeType   {square,    circle};
public    class   Shape
{
      private ShapeType   type;
      public  Shape(ShapeType t){type =   t;}
      public  static  void    DrawShape(Shape s)
      {
              if(s.type   ==  ShapeType.square)
                      (s  as  Square).Draw();
              else    if(s.type   ==  ShapeType.circle)
                      (s  as  Circle).Draw();
      }
}
public    class   Circle  :   Shape
{
      private Point   center;
      private double  radius;
      public  Circle()    :   base(ShapeType.circle)  {}
      public  void    Draw()  {/* draws   the circle  */}
}
public    class   Square  :   Shape
{
      private Point   topLeft;
      private double  side;
      public  Square()    :   base(ShapeType.square)  {}
      public  void    Draw()  {/* draws   the square  */}
}

DrawShape() нарушает OCP. Он должен знать обо всех возможных производных от класса Shape и должен изменяться всякий раз, когда создаются новые производные от Shape.

Тот факт, что Square и Circle нельзя заменить на Shape, является нарушением LSP. Это нарушение вынудило DrawShape нарушить OCP. Таким образом, нарушение LSP является латентным нарушением OCP.

Как это нарушает LSP? (В частности, почему Square и Circle нельзя заменить на Shape?)

Как нарушение LSP вызывает нарушение OCP? (Я вижу, что это напрямую нарушает OCP, но я не могу понять, как нарушение LSP вызывает нарушение OCP.)




Ответы (1)


Это не является очевидным или типичным нарушением LSP, и можно утверждать, что это вообще не нарушение LSP, но вот моя интерпретация:

Ожидается, что Shape описывается его полем type. Когда DrawShape получает объект Shape, может произойти одна из нескольких вещей. В зависимости от значения поля type он может попытаться преобразовать объект в Square и вызвать его функцию Draw или попытаться преобразовать его в Circle с тем же концом. Однако не гарантируется, что это будет работать должным образом для произвольного Shape. В частности, это будет работать только в том случае, если динамический тип объекта действительно соответствует семантическому значению его поля type. Если поле type не соответствует своему динамическому типу, при попытке выполнить динамическое приведение возникнет исключение. Это поведение DrawShape при наличии объекта Shape.

Однако, учитывая Square или Circle, есть другое ожидание. В частности, ожидается, что функция всегда будет выполнять тот или иной путь без исключения, поскольку семантическое значение поля type всегда будет соответствовать динамическому типу объекта.

Другими словами, вы можете рассматривать функцию DrawShape как имеющую четыре интересных пути выполнения для объекта Shape: исключение, возникающее при динамическом приведении Circle, исключение, возникающее при динамическом приведении Square, или успешное выполнение Square или Circle. функции рисования.

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

В качестве альтернативы можно утверждать, что нарушения LSP нет; функция по-прежнему «действует» для дочерней замены так же, как и для родителя. Squares и Circles просто имеют дополнительное значение, заключающееся в том, что поле type определенно будет соответствовать динамическому типу объекта, ограничивая результаты выполнения функции во время выполнения. Хотя это можно рассматривать как изменение ожиданий функции, это также можно рассматривать просто как наложение предварительного условия.

Изменить

Я полагаю, я забыл ответить на часть вопроса: причина, по которой это предполагаемое нарушение LSP «вызывает» нарушение OCP, заключается в том, что логика функции, которая заставляет поведение Shapes отличаться от Squares и Circles, являясь динамическим приведением к дочерним элементам, это та же самая логика, которая заставляет класс Shape зависеть от своих дочерних элементов. Таким образом, нарушая LSP с помощью условной логики о подклассах, он, в свою очередь, нарушает OCP.

Я не знаю, действительно ли я сам назвал бы этот конкретный случай ситуацией «причинности» столько же, сколько простым пересечением событий, но, возможно, это было намерением автора.

person Alexander Guyer    schedule 05.07.2019