bbv.Common.StateMachine
类是我见过的最好的状态机代码。但它缺少一件事:获得当前状态。
这是一个订单跟踪系统:
fsm = new ActiveStateMachine<States, Events>();
fsm.In(States.OrderCreated)
.On(Events.Submitted)
.Goto(States.WaitingForApproval);
fsm.In(States.WaitingForApproval)
.On(Events.Reject)
.Goto(States.Rejected);
fsm.In(States.WaitingForApproval)
.On(Events.Approve)
.Goto(States.BeingProcessed);
fsm.In(States.BeingProcessed)
.On(Events.ProcessFinished)
.Goto(States.SentByMail);
fsm.In(States.SentByMail)
.On(Events.Deliver)
.Goto(States.Delivered);
fsm.Initialize(States.OrderCreated);
fsm.Start();
fsm.Fire(Events.Submitted);
// Save this state to database
你可以很容易地看到它的工作原理。
但我想在数据库中保存订单状态。所以我将能够显示订单的状态。
我需要一个
fsm.GetCurrentState()
//show this state in the a table
方法。实际上有一种方法:我可以使用ExecuteOnEntry
并在每个州的条目上更改本地值。但是为每个州写ExecuteOnEntry
会很麻烦,因为我会重复自己!
必须有一种微妙的方式来做到这一点。
正如丹尼尔解释的那样,这是设计上的。让我解释一下原因:
状态机允许排队事件。因此,向状态机询问其当前状态可能会产生误导。它目前处于状态A,但已经有一个排队的事件将进入状态B.
此外,我认为将状态机内部状态(您在状态机定义中使用的状态)直接与状态机外部状态(您希望在数据库中保留的状态)耦合在一起是不好的设计。如果直接将这两者结合起来,就会失去在不影响外部的情况下在内部重构状态机的能力(在您的情况下是数据库)。我经常遇到我必须将状态A分成A1和A2的情况,因为我必须为它们附加不同的动作,但是它们仍然代表环境的相同状态。因此,我强烈建议您将内部和外部状态分开,无论是使用OnEntryExecute()编写,还是通过提供映射和使用扩展。这是一个扩展,它将使您获得当前状态:
public class CurrentStateExtension : ExtensionBase<State, Event>
{
public State CurrentState { get; private set; }
public override void SwitchedState(
IStateMachineInformation<State, Event> stateMachine,
IState<State, Event> oldState,
IState<State, Event> newState)
{
this.CurrentState = newState.Id;
}
}
您可以通过以下方式将扩展添加到状态机:
currentStateExtension = new CurrentStateExtension();
machine.AddExtension(currentStateExtension);
当然,您也可以直接使用此扩展来访问当前状态。为了使它更简单,让定义状态机的类实现扩展并将自身作为扩展传递。让你摆脱额外的课程。
最后一点:当你在https://groups.google.com/forum/?fromgroups#!forum/appccelerate的谷歌小组中提出有关bbv.Common(或现在称为Appccelerate)的问题时,我更容易找到问题并回答它;-)
这是设计的。我们考虑查询状态机的状态作为设计气味。但当然有例外情况。您有以下两种选择:
ExecuteOnEntry
方法保存订单状态。这反映了要走的路,因为您不希望将状态机的状态泄漏到您的业务逻辑中。StateMachine<TState, TEvent>
。这暴露了国家。丹尼尔