Это идеальный сценарий для модели состояния.
В шаблоне State ваши классы состояний должны отвечать за переходное состояние, а не только проверять правильность перехода. Кроме того, отправка переходов состояний за пределы класса заказа не является хорошей идеей и идет вразрез с шаблоном, но вы все равно можете работать с классом OrderProcessor.
Вы должны получить каждый класс состояний для реализации операции setQuantity. Класс состояний должен реализовывать все методы, которые могут быть действительными в одних состояниях, но не в других, независимо от того, предполагает ли это изменение состояния.
Нет необходимости в таких методах, как canChangeQuantity () и isValid () - классы состояний гарантируют, что экземпляры вашего заказа всегда находятся в допустимом состоянии, потому что любая операция, которая недопустима для текущего состояния, будет сброшена, если вы попробуете ее .
Свойства вашего класса Order хранятся вместе с заказом, а не с состоянием. В .Net вы бы сделали эту работу, вложив свои классы состояний в класс Order и предоставив ссылку на заказ при совершении вызовов - класс состояний будет иметь доступ к закрытым членам заказа. Если вы не работаете в .Net, вам нужно найти похожий механизм для вашего языка - например, классы друзей в C ++.
Несколько комментариев о ваших состояниях и переходах:
Состояние A отмечает, что заказ новый, количество> 0 и имеет идентификатор продукта. Для меня это означает, что вы либо предоставляете оба этих значения в конструкторе (чтобы убедиться, что ваш экземпляр запускается в допустимом состоянии, но вам не понадобится метод setQuantity), либо вам нужно начальное состояние, которое имеет assignProduct (Количество Int32, Int32 productId) метод, который будет переходить из исходного состояния в состояние A.
Точно так же вы можете рассмотреть возможность перехода в конечное состояние из состояния C после того, как поставщик заполнил цену.
Если для перехода состояния требуется присвоение двух свойств, вы можете рассмотреть возможность использования одного метода, который принимает оба свойства по параметру (а не setQuantity, за которым следует set setProductId), чтобы сделать переход явным.
Я бы также предложил более описательные имена состояний - например, вместо StateD, назовите его CanceledOrder.
Вот пример того, как я реализую этот шаблон в C #, без добавления новых состояний:
public class Order
{
private BaseState _currentState;
public Order(
Int32 quantity,
Int32 prodId)
{
Quantity = quantity;
ProductId = prodId;
_currentState = new StateA();
}
public Int32 Quantity
{
get; private set;
}
public Int32 ProductId
{
get; private set;
}
public String Supplier
{
get; private set;
}
public Decimal Price
{
get; private set;
}
public void CancelOrder()
{
_currentState.CancelOrder(this);
}
public void AssignSupplier(
String supplier)
{
_currentState.AssignSupplier(this, supplier);
}
public virtual void AssignPrice(
Decimal price)
{
_currentState.AssignPrice(this, price);
}
abstract class BaseState
{
public virtual void CancelOrder(
Order o)
{
throw new NotSupportedException(
"Invalid operation for order state");
}
public virtual void AssignSupplier(
Order o,
String supplier)
{
throw new NotSupportedException(
"Invalid operation for order state");
}
public virtual void AssignPrice(
Order o,
Decimal price)
{
throw new NotSupportedException(
"Invalid operation for order state");
}
}
class StateA : BaseState
{
public override void CancelOrder(
Order o)
{
o._currentState = new StateD();
}
public override void AssignSupplier(
Order o,
String supplier)
{
o.Supplier = supplier;
o._currentState = new StateB();
}
}
class StateB : BaseState
{
public virtual void AssignPrice(
Order o,
Decimal price)
{
o.Price = price;
o._currentState = new StateC();
}
}
class StateC : BaseState
{
}
class StateD : BaseState
{
}
}
Вы можете работать с классами обработчика заказов, но они работают с открытыми методами класса заказов и позволяют классам состояний заказа сохранять всю ответственность за переходное состояние. Если вам нужно узнать, в каком состоянии вы находитесь в данный момент (чтобы позволить процессору заказов определить, что делать), вы можете добавить свойство String Status в класс заказа и в BaseState и заставить каждый конкретный класс состояний возвращать свое имя.