Как объект, использующий шаблон состояния, должен быть переведен в следующее состояние? - PullRequest
4 голосов
/ 14 апреля 2009

У меня есть класс Order, который проходит через серию определенных состояний. Чтобы помочь в этом, я реализовал шаблон State так, что у объекта Order есть член CurrentState, который реализует интерфейс IOrderState. Затем у меня есть конкретные реализации этого интерфейса, такие как OrderStateNew, OrderStateDelivered и т. Д. И т. Д.

Мой вопрос: как правильно перевести объект Order между состояниями? Допустимо ли иметь метод Order.SetState (), который позволяет внешней службе устанавливать состояние? Критерии, которые определяют изменения состояния, хранятся внешне в объекте Order, поэтому это кажется очевидным ответом, но мне немного неловко иметь открытый метод для изменения чего-то столь фундаментального, как этот.

Дополнительные уточнения Я подумал, что было бы полезно добавить некоторые подробности о моей реализации, потому что мне интересно, правильно ли я использую шаблон в первую очередь. Вот пульсирующий API для создания и авторизации заказа

Dim orderFacade As New OrderFacade
Dim order = orderFacade.createFrom(customer)

' Add lines etc

' This will validate the order and transition it to status 'Authorised'
Dim valid = orderFacade.Authorise(order)

' This will commit the order, but only if it is at status 'Authorised'
Dim result = orderFacade.Commit()

Функция OrderFacade.Authorise () выглядит примерно так

Public Function Authorise(ByRef originalOrder As Order) As ValidationSummary

    If originalOrder.CurrentState.CanAuthorise() Then

       Dim validator = OrderValidatorFactory.createFrom(originalOrder)
       Dim valid = validator.ValidateOrder(originalOrder)

      If valid.IsValid Then
          originalOrder.SetOrderStatus(OrderStatus.Authorised)
      End If

     Return valid

    End If

 End Function

Как видите, элемент CurrentState является текущей реализацией IOrderState, которая определяет, какие действия допустимы для объекта. Интересно, должно ли это отвечать за определение перехода, а не за OrderFacade?

Ответы [ 5 ]

3 голосов
/ 14 апреля 2009

Рассмотрите возможность изменения состояния не по назначению, а по смыслу.

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

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

2 голосов
/ 14 апреля 2009

Метод SetState () будет иметь преимущества в расширении ордеров с последующими состояниями, а также в изменении инструментов - но я бы не рекомендовал его. Шаблон State предназначен для сбора поведения, характерного для отдельных состояний в отдельных классах, а не для представления интерфейсов с состоянием другим классам.

Для заказов подумайте о бизнес-событиях, которые происходят естественным образом (например, подтверждение, подтверждение, уведомление об отправке, отгрузка, счет-фактура и т. Д.) И разработайте четкий интерфейс вокруг них. Как именно вы разрабатываете интерфейс, зависит от того, как структурирована логика вашего приложения и как она используется на других уровнях. Классический ответ заключается в определении абстрактных методов для каждого бизнес-события (например, Confirm (), Acknowledge (), ShipDateChanged ()). Если вы используете, например, C # вы можете решить входить и исходить события из ваших объектов заказа. Или вы можете попробовать попробовать смесь или их комбинацию. Суть в том, что интерфейс SetOrderState () не очень нагляден и может привести к неуклюжей реализации (большие методы в каждом из ваших классов OrderState).

С другой стороны, метод SetState (), встроенный в ваши классы (вызываемый из каждого из ваших конкретных методов или событий), может быть в порядке, если у вас нет большого кода для различных изменений состояния: но я бы не стал выставить это как внешний интерфейс. Недостатком является то, что вы можете получить некоторое совпадение между методами вашего внутреннего интерфейса IOrderState и внешним интерфейсом Order.

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

1 голос
/ 14 апреля 2009

Я думаю, переход между состояниями должен быть в классе.

Например, когда вы выполняете какое-то действие в заказе, заказ знает, как перейти в другое состояние. Например:

 public void setPaid(int amount)
 {
    //Code to pay.
    this.State = new PaidState();   //State implements the IState Interface
 }

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

 public class Transformer
 {
    public static void setState(Order o, IState s)
    {
         //Change the State.
    }
 }

Или yoy может использовать Enum для установки там состояний:

 public class Transformer
 {
    public static void setState(Order o, StatesEnum s)
    {
         //Change the State.
    }
 }

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

С наилучшими пожеланиями!

1 голос
/ 14 апреля 2009

Не думаю, что для этого есть «правильный» ответ; это действительно зависит от архитектуры, которую вы выбрали для своего конечного автомата. Если бы вся логика изменения состояния была инкапсулирована в вашем классе Order, то я бы сказал, что было бы плохой практикой выставлять метод SetState. Однако, поскольку вы уже разместили некоторую логику определения состояния вне класса Order, представляется целесообразным (и необходимым) предоставить открытый метод SetState или что-то подобное.

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

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

1 голос
/ 14 апреля 2009

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

...