状态模式:当一个对象参与复杂过程时,它们的状态应如何转换?

nic*_*083 10 state design-patterns

我对状态模式的以下实现有一些疑问:

我有一个Order对象.为简单起见,我们假设它有一个数量,productId,价格和供应商.此外,订单可以转换有一组已知状态:

  • 状态a:订单是新的,数量必须> 0并且必须具有productId.价格和供应商尚未分配.
  • b:有人检查订单.它只能被取消,或者分配供应商.
  • 州c:供应商只能填写要向客户收取的价格.
  • 状态d:订单被取消.

1)Order.isValid()在状态之间变化.即,在状态中,一些操作无法完成.所以,它们看起来像:
void setQuantity(int q){
if(_state.canChangeQuantity())this.quantity = q;
否则抛出异常.
}
这是正确的,或者我应该得到每个国家实施setQuantity操作?在那种情况下,哪里会存储值?在顺序,还是国家?在后一种情况下,我将不得不在每个状态转换中复制数据?

2)orderProcessor.process(order)是一个检查order.IsValid的对象,将订单转换为某个状态,将其保存到db并执行一些自定义操作(在某些状态下,admin会通知,在其他状态下会通知客户端等) .每个州都有一个.
在StateAOrderProcessor中,通过电子邮件通知检查订单的人员,订单将转换为状态b.
现在,这将推动Order类之外的状态转换.这意味着Order有一个'setState'方法,因此每个处理器都可以改变它.这个从外面改变状态的事情听起来不太好听.这是对的吗?

3)Ahother选项是将所有验证逻辑移动到每个州的处理器,但现在我必须跟踪订单数量的变化,以查看该操作在当前状态下是否有效. 这让我的订单变得贫血.

你们觉得怎么样?你能给我一些建议来更好地设计这件事吗?

非常感谢你.

缺口

Rem*_*yth 6

这是状态模式的理想情况.

在状态模式中,您的状态类应该负责转换状态,而不仅仅是检查转换的有效性.此外,在订单类之外推送状态转换不是一个好主意,并且违背了模式,但您仍然可以使用OrderProcessor类.

您应该让每个州类实现setQuantity操作.状态类应该实现在某些状态下可能有效但在其他状态下无效的所有方法,无论它是否涉及状态的改变.

不需要诸如canChangeQuantity()和isValid()之类的方法 - 状态类确保您的订单实例始终处于有效状态,因为如果您尝试,任何对当前状态无效的操作都将抛出.

Order类的属性与订单一起存储,而不是状态.在.Net中,您可以通过在Order类中嵌套状态类并在进行调用时提供对命令的引用来完成此工作 - 然后状态类将可以访问订单的私有成员.如果您不在.Net中工作,则需要为您的语言找到类似的机制 - 例如,C++中的朋友类.

关于你的状态和过渡的一些评论:

  • 状态A指出订单是新的,数量> 0并且它具有产品ID.对我来说,这意味着你要么在构造函数中提供这两个值(以确保你的实例在有效状态下启动,但你不需要setQuantity方法),或者你需要一个具有assignProduct的初始状态(Int32 quantity,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
  {
  }
 }
Run Code Online (Sandbox Code Playgroud)

您可以使用订单处理器类,但它们使用订单类上的公共方法,并让订单的状态类对转换状态负全部责任.如果您需要知道当前处于什么状态(允许订单处理器确定要执行的操作),您可以在订单类和BaseState上添加String Status属性,并让每个具体的状态类返回其名称.


djn*_*jna -1

你们有几个不同的课程,每个州一个。

BaseOrder {
    //  common getters
    // persistence capabilities
}

NewOrder extends BaseOrder {
    // setters
    CheckingOrder placeOrder();
} 

CheckingOrder extends BaseOrder {
     CancelledOrder cancel();
     PricingOrder assignSupplier();
}
Run Code Online (Sandbox Code Playgroud)

等等。这个想法是,需要特定状态下的订单的代码只会获取正确类的对象,因此不需要状态检查。只想在任何状态情况下对订单进行操作的代码使用 BaseClass。