Является ли это нарушением Принципа замещения Лискова?

Скажем, у нас есть список объектов задачи и подтип ProjectTask. Задачи могут быть закрыты в любое время, кроме ProjectTasks, которые не могут быть закрыты, когда у них есть статус Started. Пользовательский интерфейс должен обеспечить возможность закрытия начального ProjectTask, который никогда не будет доступен, но некоторые меры предосторожности присутствуют в домене:

 public class Task
{
     public Status Status { get; set; }

     public virtual void Close()
     {
         Status = Status.Closed;
     }
}

public class ProjectTask : Task
{
     public override void Close()
     {
          if (Status == Status.Started) 
              throw new Exception("Cannot close a started Project Task");

          base.Close();
     }
}

Теперь при вызове Close() в задаче есть вероятность, что вызов завершится неудачно, если это ProjectTask с начальным статусом, когда это не будет если это была базовая задача. Но это бизнес-требования. Он должен потерпеть неудачу. Можно ли это расценить как нарушение принципа замены Лискова ?

124 голоса | спросил Paul T Davies 17 +04002012-10-17T00:36:38+04:00312012bEurope/MoscowWed, 17 Oct 2012 00:36:38 +0400 2012, 00:36:38

10 ответов


167

Да, это нарушение LSP. Принцип замены Лискова требует , чтобы

  • Предварительные условия не могут быть усилены в подтипе.
  • Постусловия не могут быть ослаблены в подтипе.
  • Инварианты супертипа должны быть сохранены в подтипе.
  • Ограничение истории («правило истории»). Объекты считаются модифицируемыми только через их методы (инкапсуляция). Поскольку подтипы могут вводить методы, отсутствующие в супертипе, введение этих методов может позволить изменения состояния в подтипе, которые недопустимы в супертипе. Ограничение истории запрещает это.

Ваш пример нарушает первое требование, усиливая предварительное условие для вызова метода Close().

Вы можете исправить это, доведя усиленное предварительное условие до верхнего уровня иерархии наследования:

 public class Task {
    public Status Status { get; set; }
    public virtual bool CanClose() {
        return true;
    }
    public virtual void Close() {
        Status = Status.Closed;
    }
}

Указывая, что вызов Close() действителен только в состоянии, когда CanClose() возвращает true, вы делаете pre- условие применяется к Task, а также к ProjectTask, исправляя нарушение LSP:

 public class ProjectTask : Task {
    public override bool CanClose() {
        return Status != Status.Started;
    }
    public override void Close() {
        if (Status == Status.Started) 
            throw new Exception("Cannot close a started Project Task");
        base.Close();
    }
}
ответил dasblinkenlight 17 +04002012-10-17T00:45:59+04:00312012bEurope/MoscowWed, 17 Oct 2012 00:45:59 +0400 2012, 00:45:59
78

Да. Это нарушает LSP.

Мое предложение состоит в том, чтобы добавить метод /свойство CanClose > в базовую задачу, поэтому любая задача может определить, может ли задача в этом состоянии быть закрыта. Это также может служить причиной. И удалите виртуальную версию из CanClose.

Основываясь на моем комментарии:

 Close
ответил Euphoric 17 +04002012-10-17T00:44:30+04:00312012bEurope/MoscowWed, 17 Oct 2012 00:44:30 +0400 2012, 00:44:30
22

Принцип подписи Лискова гласит, что базовый класс должен быть заменен любым из его подклассов без изменения каких-либо желательных свойств программы. Поскольку только ProjectTask создает исключение при закрытии, программа должна быть заменена на acommodate для этого, если ProjectTask будет использоваться при замене Task , Так что это нарушение.

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

ответил Tulains Córdova 17 +04002012-10-17T00:49:40+04:00312012bEurope/MoscowWed, 17 Oct 2012 00:49:40 +0400 2012, 00:49:40
17

Нарушение LSP требует трех сторон. Тип T, подтип S и программа P, которая использует T, но предоставляется экземпляр S.

Ваш вопрос предоставил T (Task) и S (ProjectTask), но не P. Итак, ваш вопрос неполный и ответ квалифицирован: если существует P, который не ожидает исключения, то для этого P вы имеют нарушение LSP. Если каждый P ожидает исключения, то нарушение LSP отсутствует.

Тем не менее, do имеет нарушение SRP . Тот факт, что состояние задачи может быть изменено, и политика , что определенные задачи в определенных состояниях должны не меняться на другие состояния, являются двумя очень разными обязанностями.

  • Ответственность 1: Представление задачи.
  • Ответственность 2: Внедрение политик, которые изменяют состояние задач.

Эти две обязанности меняются по разным причинам и поэтому должны быть в отдельных классах. Задачи должны учитывать факт выполнения задачи и данные, связанные с задачей. TaskStatePolicy должен обрабатывать процесс перехода задач из состояния в состояние в данном приложении.

ответил Robert Martin 4 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowWed, 04 Sep 2013 20:00:15 +0400 2013, 20:00:15
15

Этот может или не может быть нарушением LSP.

Серьезно. Услышьте меня.

Если вы следуете LSP, объекты типа ProjectTask должны вести себя как объекты типа Task, как ожидается, будут себя вести.

Проблема с вашим кодом заключается в том, что вы не задокументировали, как будут вести себя объекты типа Task. Вы написали код, но никаких контрактов. Я добавлю контракт для Task.Close. В зависимости от контракта, который я добавляю, код для ProjectTask.Close либо выполняет, либо не выполняет LSP.

Учитывая следующий контракт для Task.Close, код для ProjectTask.Close не соответствует LSP:

      // Behaviour: Moves the task to the closed state
     // and does not throw any Exception.
     // Default behaviour: Moves the task to the closed state
     // and does not throw any Exception.
     public virtual void Close()
     {
         Status = Status.Closed;
     }

Учитывая следующий контракт для Task.Close, код для ProjectTask.Close выполняет , следуя LSP:

     // Behaviour: Moves the task to the closed status if possible.
     // If this is not possible, this method throws an Exception
     // and leaves the status unchanged.
     // Default behaviour: Moves the task to the closed state
     // and does not throw any Exception.
     public virtual void Close()
     {
         Status = Status.Closed;
     }

Методы, которые могут быть переопределены, должны быть документированы двумя способами:

  • Документы «Поведение», на которые может полагаться клиент, который знает объект-получатель, - это Task, но не знают, какой класс является прямым экземпляром. Это также говорит о конструкторах подклассов, которые переопределяют разумные и которые не являются разумными.

  • Документы «Поведение по умолчанию», на которые может полагаться клиент, который знает, что объект-получатель является прямым экземпляром Task (то есть, что вы получаете, если используете new Task(). Он также сообщает разработчикам подклассов, какое поведение будет унаследовано, если они не переопределяют этот метод.

Теперь должны выполняться следующие отношения:

  • Если S является подтипом T, документированное поведение S должно уточнять документированное поведение T.
  • Если S является подтипом (или равным) T, поведение кода S должно улучшить документированное поведение T.
  • Если S является подтипом (или равным) T, поведение по умолчанию S должно уточнять документированное поведение T.
  • Фактическое поведение кода для класса должно уточнить его документированное поведение по умолчанию.
ответил Theodore Norvell 30 J0000006Europe/Moscow 2015, 17:28:19
6

Это не является нарушением принципа замены Лискова.

Принцип замещения Лискова говорит:

  

Пусть q (x) - свойство, доказываемое об объектах x типа T . Пусть S - подтип T . Тип S нарушает принцип замены Лискова, если существует объект y типа S , так что q (y) не доказуемо.

Причина, почему ваша реализация подтипа не является нарушением Принципа замещения Лискова, довольно проста: ничего не может быть доказано о том, что действительно делает Task::Close(). Конечно, ProjectTask::Close() генерирует исключение, когда Status == Status.Started, но может Status = Status.Closed в Task :: Close ().

ответил Oswald 18 +04002012-10-18T23:17:05+04:00312012bEurope/MoscowThu, 18 Oct 2012 23:17:05 +0400 2012, 23:17:05
4

Да, это нарушение.

Я бы предположил, что у вас есть иерархия назад. Если не все Task закрываются, то close() не принадлежит Task. Возможно, вам нужен интерфейс, CloseableTask, который может реализовать весь не ProjectTasks.

ответил Tom G 17 +04002012-10-17T00:42:22+04:00312012bEurope/MoscowWed, 17 Oct 2012 00:42:22 +0400 2012, 00:42:22
3

В дополнение к проблеме LSP, похоже, что она использует исключения для управления потоком программы (я должен предположить, что вы где-то поймаете это тривиальное исключение и сделаете некоторый пользовательский поток, а не разрешите его сбой вашего приложения).

Кажется, это будет хорошим местом для реализации шаблона State для TaskState и позволить объектам состояния управлять действительными переходами.

ответил Ed Hastings 10 62012vEurope/Moscow11bEurope/MoscowSat, 10 Nov 2012 07:29:24 +0400 2012, 07:29:24
1

Мне не хватает здесь важной вещи, связанной с LSP и Design by Contract - в предварительных условиях, это звонящий, чья ответственность заключается в том, чтобы убедиться в соблюдении предварительных условий. Вызываемый код в теории DbC не должен проверять предварительное условие. Контракт должен указывать, когда задача может быть закрыта (например, CanClose возвращает True), а затем вызывающий код должен обеспечить выполнение предварительного условия, прежде чем он вызовет Close ().

ответил Ezoela Vacca 26 J000000Thursday18 2018, 20:01:10
0

Да, это явное нарушение LSP.

Некоторые люди утверждают, что сделать явным в базовом классе, что подклассы могут генерировать исключения, сделает это приемлемым, но я не думаю, что это правда. Независимо от того, что вы документируете в базовом классе или на каком уровне абстракции вы перемещаете код, предпосылки все еще будут усилены в подклассе, потому что вы добавляете к нему часть «Невозможно закрыть запущенную задачу проекта». Это не то, что вы можете решить с помощью обходного пути, вам нужна другая модель, которая не нарушает LSP (или нам нужно ослабить ограничение «условия не могут быть усилены»).

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

ответил inf3rno 10 +03002017-10-10T06:56:34+03:00312017bEurope/MoscowTue, 10 Oct 2017 06:56:34 +0300 2017, 06:56:34

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132