一 什么是有限状态机
有限状态机,英⽂翻译是 Finite State Machine,缩写为 FSM,简称为状态机。
状态机不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。
已订单交易为例:
1.1四大概念
下面来给出状态机的四大概念。
- State ,状态。一个状态机至少要包含两个状态。例如上商家交易有 已下单、已支付、已发货等多种状态。
- Event,事件。事件也称为转移条件(Transition Condition)。例如 客户下单、 客户完成支付、商家发货 都是一个事件。
- Action ,动作。事件发生以后要执行动作。例如用户支付,扣减用户余额就是动作。编程的时候,一个 Action 一般就对应一个函数。不过动作不是必须的,也可能只转移状态,不执⾏任何动作。
- Transition ,变换。也就是从一个状态变化为另一个状态。例如 订单从“已支付”转换到“已发货”。
二 状态机的实现方法
将上面业务流程翻译成骨架代码:
type State int64const StateWaitingPayment State = 1 //等待支付
const StateWaitingShip State = 2 //支付成功待发货// 订单状态机
type LeaseStateMachine struct {State State //订单状态
}// 订单支付成功
func (p *LeaseStateMachine) EventPaySuccess() {//todo
}// 取消了订单
func (p *LeaseStateMachine) EventCancelOrder() {//todo
}// 商家发货
func (p *LeaseStateMachine) EventShipped() {//todo
}// 确认收货
func (p *LeaseStateMachine) EventConfirmReceipt() {//todo
}
2.1 分支逻辑
最简单直接的实现⽅式是,参照状态转移 图,将每⼀个状态转移,直译成代码。这样编写的代码会包含⼤量的 if-else 或 switch-case 分⽀判断逻辑。
type State int64const StateWaitingPayment State = 1 //等待支付
const StateWaitingShip State = 2 //支付成功待发货
const StateWaitingShipped State = 3 //发货成功
const StateWaitingOrderSuccess State = 4 //订单结束
const StateWaitingOrderCancel State = 5 //订单取消// 租赁订单状态机
type LeaseStateMachine struct {State State //订单状态
}// 订单支付成功
func (p *LeaseStateMachine) EventPaySuccess() {if p.State == StateWaitingPayment {p.State = StateWaitingShip}
}// 取消了订单
func (p *LeaseStateMachine) EventCancelOrder() {if p.State == StateWaitingShip ||p.State == StateWaitingPayment {p.State = StateWaitingOrderCancel}
}// 商家发货
func (p *LeaseStateMachine) EventShipped() {if p.State == StateWaitingShip {p.State = StateWaitingShipped}
}// 确认收货
func (p *LeaseStateMachine) EventConfirmReceipt() {if p.State == StateWaitingShipped {p.State = StateWaitingOrderSuccess}
}
2.2 查表法
除了⽤状态转移图来表示之外,状态机还可以⽤⼆维表来表示;将上面的状态图转换成二维表如下
当前状态/事件 | E支付成功 | E发货 | E取消订单 | E确认收货 |
---|---|---|---|---|
等待支付 | 支付成功待发货 | / | / | / |
支付成功待发货 | / | 发货成功 | 订单取消 | / |
已发货 | / | / | / | 订单结束 |
订单结束 | / | / | / | / |
订单取消 | / | / | / | / |
使用查表表修改上述代码:
type State int64const StateWaitingPayment State = 1 //等待支付
const StateWaitingShip State = 2 //支付成功待发货
const StateWaitingShipped State = 3 //发货成功
const StateWaitingOrderSuccess State = 4 //订单结束
const StateWaitingOrderCancel State = 5 //订单取消type Event int64const (EventPay Event = 1 //支付事件EventShip Event = 2 //发货 事件EventCancel Event = 3 //取消订单 事件EventConfirmReceipt Event = 4 //确认收货
)// 状态二维表配置
var StateTable map[State]map[Event]State = map[State]map[Event]State{StateWaitingPayment: {EventPay: StateWaitingShip, //待支付订单 ,支付事件 => 已支付},StateWaitingShip: {EventShip: StateWaitingShipped,EventCancel: StateWaitingOrderCancel,},//.......
}// 租赁订单状态机
type LeaseStateMachine struct {State State //订单状态}// 订单支付成功
func (p *LeaseStateMachine) EventPaySuccess() {p.ExecEventConfirmReceipt(EventPay)
}// 取消了订单
func (p *LeaseStateMachine) EventCancelOrder() {p.ExecEventConfirmReceipt(EventCancel)
}// 商家发货
func (p *LeaseStateMachine) EventShipped() {p.ExecEventConfirmReceipt(EventShip)
}// 确认收货
func (p *LeaseStateMachine) EventConfirmReceipt() {p.ExecEventConfirmReceipt(EventConfirmReceipt)
}// 执行事件
func (p *LeaseStateMachine) ExecEventConfirmReceipt(event Event) {EventNewStateTable, ok := StateTable[p.State]if ok {newState, ok := EventNewStateTable[event]if ok {p.State = newState}}
}
在查表法的代码实现中,事件触发的动作只是简单状态变换,所以⽤⼀个 int 类型 的⼆维数组 actionTable 就能表示。但是,如果要执⾏ 动作并⾮这么简单,⽽是⼀系列复杂的逻辑操作(⽐如加减积分、写数据库,还有可能发 送消息通知等等),我们就没法⽤如此简单的⼆维数组来表示了。
2.3状态模式
状态模式通过将事件触发的状态转移和动作执⾏,拆分到不同的状态类中,来避免分⽀判断
逻辑。
1.定义interface 所有事件
type ILeaseState interface {//定义事件EventPay() //支付事件EventShip() //发货事件EventCancel() //取消订单事件EventConfirmReceipt() //确认收货事件
}
2.状态类实现 事件对应的action
将事件对饮的代码逻辑被分散到各个状态类中。
//==================================================================
// 待支付状态
type StateWaitingPaymentImp struct{}// 订单支付成功
func (p *StateWaitingPaymentImp) EventPay() {//todo 更新订单状态
}// 发货
func (p *StateWaitingPaymentImp) EventShip() {//不做处理
}// 取消
func (p *StateWaitingPaymentImp) EventCancel() {//todo 取消
}// 确认收货事件
func (p *StateWaitingPaymentImp) EventConfirmReceipt() {//不做处理
}
//==================================================================
// 支付成功 状态
type StateWaitingShipImp struct{}// 订单支付成功
func (p *StateWaitingShipImp) EventPay() {//不做任何处理
}// 发货
func (p *StateWaitingShipImp) EventShip() {//更新订单未发货
}// 取消
func (p *StateWaitingShipImp) EventCancel() {//更新订单未发货
}// 确认收货事件
func (p *StateWaitingShipImp) EventConfirmReceipt() {//不做处理
}
//===============================================================
//........其他状态对应的事件
三 总结
实现方法对比
实现方法 | 优点 | 缺点 |
---|---|---|
分支逻辑 |
|
|
查表法 |
|
|
状态模式 |
|
|
像电商下单这种状态并不多,状态转移也⽐较简单,但事件触发执⾏的动作包含的业务逻辑可能会⽐较复杂,更加推荐使⽤状态模式来实现。
像游戏⽐较复杂的状态机,包含的状态⽐较多,优先推荐使⽤查表法,