状态模式:当对象参与复杂过程时,其状态应如何转换?

发布于 2024-08-04 00:39:30 字数 971 浏览 1 评论 0原文

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

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

  • 状态 a:订单是新的,数量必须大于 1。 0 并且必须有productId。价格和供应商尚未指定。
  • 状态b:有人检查订单。只能取消,或者指定供应商。
  • 状态c:供应商只能填写要向客户收取的价格。
  • 状态d:订单被取消。
  1. Order.isValid() 在状态之间变化。即,在状态a 下,某些操作无法完成。所以,它们看起来像:
    void setQuantity(int q) {
    if (_state.canChangeQuantity()) this.quantity = q;
    否则抛出异常。
    }
    这是正确的吗?或者我应该让每个状态实现 setQuantity 操作?那么,该值将存储在哪里?在命令中,还是在状态中?在后一种情况下,我必须在每次状态转换中复制数据?

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

  3. 另一个选择是将所有验证逻辑移至每个状态的处理器,但现在我必须跟踪订单数量何时更改,以查看该操作在当前状态下是否有效。 这让我觉得这个秩序有点贫血。

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

I have some doubts about the following implementation of the state pattern:

I have an Order object. For simplicity, let's suppose it has a quantity, productId, price and supplier. Also, there are a set of known states in which the order can transition:

  • state a: order is new, quantity must be > 0 and must have productId. Price and supplier are not yet assigned.
  • state b: someone checks the order. It can only be cancelled, or have the supplier assigned.
  • state c: supplier can only fill in the price to be charged to the client.
  • State d: the order is cancelled.
  1. Order.isValid() changes between states. Ie, in state a some operations cannot be done. So, they look like:
    void setQuantity(int q) {
    if (_state.canChangeQuantity()) this.quantity = q;
    else throw an exception.
    }
    Is this right, or should i get each state to implement setQuantity operation? In that case, where will be stored the value? In the order, or the state? In the latter case, i will have to copy the data in each state transition?

  2. orderProcessor.process(order) is an object that checks order.IsValid, transition the order to some state, saves it to the db and perform some custom actions (in some states, admin is notified, in others the client, etc). I have one for each state.
    In StateAOrderProcessor, the person that checks the order is notified via email, and the order is transitioned to state b.
    Now, this pushes the state transitions outside the Order class. That means that Order has a 'setState' method, so each processor can change it. This thing to change the state from the outside does not sounds nice. Is that right?

  3. Another option is to move all the validation logic to each state's processor, but now i have to track when a order's quantity was changed to see if that operation is valid in the current state.
    That leaves the order kind of anemic to me.

What do you think guys? Can you give me some advice to design this thing better?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

萌吟 2024-08-11 00:39:30

这是状态模式的理想场景。

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

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

不需要 canChangeQuantity() 和 isValid() 等方法 - 状态类确保您的订单实例始终处于有效状态,因为任何对当前状态无效的操作在您尝试时都会抛出异常。

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

关于您的状态和转换的一些评论:

  • 状态 A 指出订单是新的,数量 >0 并且具有产品 ID。对我来说,这意味着您要么在构造函数中提供这两个值(以确保您的实例以有效状态启动,但您不需要 setQuantity 方法),或者您需要一个具有 allocateProduct 的初始状态(Int32数量,Int32产品Id)方法,将从初始状态转换到状态A。

  • 同样,您可能需要考虑在供应商填写价格后从状态C转换到最终状态。

  • 您的状态转换需要分配两个属性,您可能需要考虑使用通过参数接受这两个属性的单个方法(而不是 setQuantity 后跟 setSetProductId),以使转换明确。

  • 我还会建议更具描述性的状态名称 - 例如,将其称为 CanceledOrder,而不是 StateD。

下面是我如何在 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
  {
  }
 }

您可以使用订单处理器类,但它们使用订单类上的公共方法,并让订单的状态类承担所有责任过渡状态。如果您需要知道当前处于什么状态(以允许订单处理器确定要做什么),您可以在订单类和 BaseState 上添加一个 String Status 属性,并让每个具体状态类返回其名称。

This is an ideal scenario for the state pattern.

In the State pattern, your state classes should be responsible for transitioning state, not just checking the validity of the transition. In addition, pushing state transitions outside of the order class is not a good idea and goes against the pattern, but you can still work with an OrderProcessor class.

You should get each state class to implement the setQuantity operation. The state class should implement all methods that may be valid in some states but not in others, whether or not it involves a change of state.

There is no need for methods such as canChangeQuantity() and isValid() - the state classes ensure your order instances are always in a valid state, because any operation that is not valid for the current state will throw if you try it.

Your Order class's properties are stored with the order, not the state. In .Net, you'd make this work by nesting your state classes inside the Order class and providing a reference to the order when making calls - the state class will then have access to the order's private members. If you're not working in .Net, you need to find a similar mechanism for your language - for example, friend classes in C++.

A few comments on your states and transitions:

  • State A notes that the order is new, quantity is >0 and it has a product id. To me, this means you're either providing both of those values in the constructor (to ensure your instance starts off in a valid state, but you wouldn't need a setQuantity method), or you need an initial state which has an assignProduct(Int32 quantity, Int32 productId) method that will transition from the initial state to state A.

  • Similarly, you may want to consider an end state to transition to from state C, after the supplier has filled in the price.

  • If your state transition requires the assignment of two properties, you may want to consider using a single method that accepts both properties by parameter (and not setQuantity followed by set setProductId), to make the transition explicit.

  • I would also suggest more descriptive state names - for example, instead of StateD, call it CanceledOrder.

Here's an example of how I'd implement this pattern in C#, without adding any new states:

 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
  {
  }
 }

You can work with your order processor classes, but they work with the public methods on the order class and let the order's state classes keep all responsibility for transitioning state. If you need to know what state you're in currently (to allow the order processor to determine what to do), you might add a String Status property on the order class and on BaseState, and have each concrete state class return its name.

森林散布 2024-08-11 00:39:30

更改当前状态对象可以直接从状态对象、订单甚至外部源(处理器)来完成,尽管不常见。

根据状态模式,Order 对象将所有请求委托给当前 OrderState 对象。如果 setQuantity() 是特定于状态的操作(在您的示例中),那么每个 OrderState 对象都应该实现它。

Changing the current state object can be done directly from a state object, from the Order and even OK from an external source (processor), though unusual.

According to the State pattern the Order object delegates all requests to the current OrderState object. If setQuantity() is a state-specific operation (it is in your example) then each OrderState object should implement it.

缱绻入梦 2024-08-11 00:39:30

为了使状态模式发挥作用,上下文对象必须公开状态类可以使用的接口。至少必须包含一个 changeState(State) 方法。恐怕这只是该模式的局限性之一,也是它并不总是有用的一个可能原因。使用状态模式的秘诀是使状态所需的接口尽可能小并限制在严格的范围内。

(1) 拥有 canChangeQuantity 方法可能比让所有状态都实现 setQuantity 更好。如果某些州正在做比抛出异常更复杂的事情,则可能不会遵循此建议。

(2)setState方法是不可避免的。然而,它的范围应该尽可能严格。在 Java 中,这可能是 Package 范围,在 .Net 中,这可能是 Assembly(内部)范围。

(3) 关于验证的问题提出了何时进行验证的问题。在某些情况下,明智的做法是允许客户端将属性设置为无效值,并且仅在执行某些处理时才验证它们。在这种情况下,每个状态都有一个验证整个上下文的“isValid()”方法是有意义的。在其他情况下,您想要更直接的错误,在这种情况下,我将创建一个 isQuantityValid(qty)isPriceValid(price) ,它们将在更改之前由 set 方法调用值,如果返回 false 则抛出异常。我总是将这两个称为早期验证和后期验证,如果不了解更多关于您在做什么的情况,很难说出您需要哪一个。

In order for the state pattern to work, the context object has to expose an interface that the state classes can use. At a bare minimum this will have to include a changeState(State) method. This I'm afraid is just one of the limitations of the pattern and is a possible reason why it is not always useful. The secret to using the state pattern is to keep the interface required by the states as small as possible and restricted to a tight scope.

(1) Having a canChangeQuantity method is probably better than having all states implement a setQuantity. If some states are doing something more complex than throwing an exception, this advice may not follow.

(2) The setState method is unavoidable. It should, however, be kept as tightly scoped as possible. In Java this would probably be Package scope, in .Net it would be Assembly (internal) scope.

(3) The point about validation raises the question of when you do validation. In some cases, it is sensible to allow the client to set properties to invalid values and only validate them when you do some processing. In this case each state having an 'isValid()' method that validates the whole context makes sense. In other cases you want a more immediate error in which case I would create an isQuantityValid(qty) and isPriceValid(price) that would be called by the set methods before changing the values, if they return false throw an exception. I've always called these two Early and Late Validation and it is not easy to say which you need without knowing more about what you are up to.

姜生凉生 2024-08-11 00:39:30

我会将信息存储在 Order 类中,并将指向 Order 实例的指针传递给状态。像这样的东西:


class Order {
  setQuantity(q) {
    _state.setQuantity(q);
  } 
}

StateA {
  setQuantity(q) {
    _order.q = q;
  }
}

StateB {
  setQuantity(q) {
    throw exception;
  }
}

I would store information in the Order class, and pass a pointer to the Order instance to the state. Something like this:


class Order {
  setQuantity(q) {
    _state.setQuantity(q);
  } 
}

StateA {
  setQuantity(q) {
    _order.q = q;
  }
}

StateB {
  setQuantity(q) {
    throw exception;
  }
}

无远思近则忧 2024-08-11 00:39:30

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

BaseOrder {
    //  common getters
    // persistence capabilities
}

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

CheckingOrder extends BaseOrder {
     CancelledOrder cancel();
     PricingOrder assignSupplier();
}

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

Do you have several different classes, one per state.

BaseOrder {
    //  common getters
    // persistence capabilities
}

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

CheckingOrder extends BaseOrder {
     CancelledOrder cancel();
     PricingOrder assignSupplier();
}

and so on. The idea is that code that needs orders in a particular state only gets objects of the right class, so no state checks are needed. Code that just wants to operate on orders in any state case use the BaseClass.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文