当前位置:   article > 正文

设计模式-状态模式 golang实现_golang 状态机

golang 状态机

目录

目录

一 什么是有限状态机 

1.1四大概念

二 状态机的实现方法

2.1 分支逻辑

2.2 查表法

2.3状态模式

三 总结

3.1实现方法对比

3.2状态机设计基本原则

3.3状态机常⻅设计误区



一 什么是有限状态机 

有限状态机,英⽂翻译是 Finite State Machine,缩写为 FSM,简称为状态机。

状态机不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。

已订单交易为例:

1.1四大概念

下面来给出状态机的四大概念。

  1. State ,状态。一个状态机至少要包含两个状态。例如上商家交易有 已下单、已支付、已发货等多种状态。
  2. Event,事件。事件也称为转移条件(Transition Condition)。例如 客户下单、 客户完成支付、商家发货 都是一个事件。
  3. Action ,动作。事件发生以后要执行动作。例如用户支付,扣减用户余额就是动作。编程的时候,一个 Action 一般就对应一个函数。不过动作不是必须的,也可能只转移状态,不执⾏任何动作。
  4. Transition ,变换。也就是从一个状态变化为另一个状态。例如 订单从“已支付”转换到“已发货”。

二 状态机的实现方法

将上面业务流程翻译成骨架代码:

  1. type State int64
  2. const StateWaitingPayment State = 1 //等待支付
  3. const StateWaitingShip State = 2 //支付成功待发货
  4. // 订单状态机
  5. type LeaseStateMachine struct {
  6. State State //订单状态
  7. }
  8. // 订单支付成功
  9. func (p *LeaseStateMachine) EventPaySuccess() {
  10. //todo
  11. }
  12. // 取消了订单
  13. func (p *LeaseStateMachine) EventCancelOrder() {
  14. //todo
  15. }
  16. // 商家发货
  17. func (p *LeaseStateMachine) EventShipped() {
  18. //todo
  19. }
  20. // 确认收货
  21. func (p *LeaseStateMachine) EventConfirmReceipt() {
  22. //todo
  23. }

2.1 分支逻辑

最简单直接的实现⽅式是,参照状态转移 图,将每⼀个状态转移,直译成代码。这样编写的代码会包含⼤量的 if-else 或 switch-case 分⽀判断逻辑。

  1. type State int64
  2. const StateWaitingPayment State = 1 //等待支付
  3. const StateWaitingShip State = 2 //支付成功待发货
  4. const StateWaitingShipped State = 3 //发货成功
  5. const StateWaitingOrderSuccess State = 4 //订单结束
  6. const StateWaitingOrderCancel State = 5 //订单取消
  7. // 租赁订单状态机
  8. type LeaseStateMachine struct {
  9. State State //订单状态
  10. }
  11. // 订单支付成功
  12. func (p *LeaseStateMachine) EventPaySuccess() {
  13. if p.State == StateWaitingPayment {
  14. p.State = StateWaitingShip
  15. }
  16. }
  17. // 取消了订单
  18. func (p *LeaseStateMachine) EventCancelOrder() {
  19. if p.State == StateWaitingShip ||
  20. p.State == StateWaitingPayment {
  21. p.State = StateWaitingOrderCancel
  22. }
  23. }
  24. // 商家发货
  25. func (p *LeaseStateMachine) EventShipped() {
  26. if p.State == StateWaitingShip {
  27. p.State = StateWaitingShipped
  28. }
  29. }
  30. // 确认收货
  31. func (p *LeaseStateMachine) EventConfirmReceipt() {
  32. if p.State == StateWaitingShipped {
  33. p.State = StateWaitingOrderSuccess
  34. }
  35. }

2.2 查表法

除了⽤状态转移图来表示之外,状态机还可以⽤⼆维表来表示;将上面的状态图转换成二维表如下

当前状态/事件

E支付成功

E发货E取消订单E确认收货
等待支付支付成功待发货///
支付成功待发货/发货成功订单取消/
已发货///订单结束
订单结束////
订单取消////

使用查表表修改上述代码:
 

  1. type State int64
  2. const StateWaitingPayment State = 1 //等待支付
  3. const StateWaitingShip State = 2 //支付成功待发货
  4. const StateWaitingShipped State = 3 //发货成功
  5. const StateWaitingOrderSuccess State = 4 //订单结束
  6. const StateWaitingOrderCancel State = 5 //订单取消
  7. type Event int64
  8. const (
  9. EventPay Event = 1 //支付事件
  10. EventShip Event = 2 //发货 事件
  11. EventCancel Event = 3 //取消订单 事件
  12. EventConfirmReceipt Event = 4 //确认收货
  13. )
  14. // 状态二维表配置
  15. var StateTable map[State]map[Event]State = map[State]map[Event]State{
  16. StateWaitingPayment: {
  17. EventPay: StateWaitingShip, //待支付订单 ,支付事件 => 已支付
  18. },
  19. StateWaitingShip: {
  20. EventShip: StateWaitingShipped,
  21. EventCancel: StateWaitingOrderCancel,
  22. },
  23. //.......
  24. }
  25. // 租赁订单状态机
  26. type LeaseStateMachine struct {
  27. State State //订单状态
  28. }
  29. // 订单支付成功
  30. func (p *LeaseStateMachine) EventPaySuccess() {
  31. p.ExecEventConfirmReceipt(EventPay)
  32. }
  33. // 取消了订单
  34. func (p *LeaseStateMachine) EventCancelOrder() {
  35. p.ExecEventConfirmReceipt(EventCancel)
  36. }
  37. // 商家发货
  38. func (p *LeaseStateMachine) EventShipped() {
  39. p.ExecEventConfirmReceipt(EventShip)
  40. }
  41. // 确认收货
  42. func (p *LeaseStateMachine) EventConfirmReceipt() {
  43. p.ExecEventConfirmReceipt(EventConfirmReceipt)
  44. }
  45. // 执行事件
  46. func (p *LeaseStateMachine) ExecEventConfirmReceipt(event Event) {
  47. EventNewStateTable, ok := StateTable[p.State]
  48. if ok {
  49. newState, ok := EventNewStateTable[event]
  50. if ok {
  51. p.State = newState
  52. }
  53. }
  54. }

在查表法的代码实现中,事件触发的动作只是简单状态变换,所以⽤⼀个 int 类型 的⼆维数组 actionTable 就能表示。但是,如果要执⾏ 动作并⾮这么简单,⽽是⼀系列复杂的逻辑操作(⽐如加减积分、写数据库,还有可能发 送消息通知等等),我们就没法⽤如此简单的⼆维数组来表示了。

2.3状态模式

状态模式通过将事件触发的状态转移和动作执⾏,拆分到不同的状态类中,来避免分⽀判断

逻辑。
 

1.定义interface 所有事件

  1. type ILeaseState interface {
  2. //定义事件
  3. EventPay() //支付事件
  4. EventShip() //发货事件
  5. EventCancel() //取消订单事件
  6. EventConfirmReceipt() //确认收货事件
  7. }

2.状态类实现 事件对应的action

将事件对饮的代码逻辑被分散到各个状态类中。

  1. //==================================================================
  2. // 待支付状态
  3. type StateWaitingPaymentImp struct{}
  4. // 订单支付成功
  5. func (p *StateWaitingPaymentImp) EventPay() {
  6. //todo 更新订单状态
  7. }
  8. // 发货
  9. func (p *StateWaitingPaymentImp) EventShip() {
  10. //不做处理
  11. }
  12. // 取消
  13. func (p *StateWaitingPaymentImp) EventCancel() {
  14. //todo 取消
  15. }
  16. // 确认收货事件
  17. func (p *StateWaitingPaymentImp) EventConfirmReceipt() {
  18. //不做处理
  19. }
  20. //==================================================================
  21. // 支付成功 状态
  22. type StateWaitingShipImp struct{}
  23. // 订单支付成功
  24. func (p *StateWaitingShipImp) EventPay() {
  25. //不做任何处理
  26. }
  27. // 发货
  28. func (p *StateWaitingShipImp) EventShip() {
  29. //更新订单未发货
  30. }
  31. // 取消
  32. func (p *StateWaitingShipImp) EventCancel() {
  33. //更新订单未发货
  34. }
  35. // 确认收货事件
  36. func (p *StateWaitingShipImp) EventConfirmReceipt() {
  37. //不做处理
  38. }
  39. //===============================================================
  40. //........其他状态对应的事件

三 总结

3.1实现方法对比

实现方法优点缺点
分支逻辑
  • 简单、直接,易理解。
  • 对简单的状态机首选该方法实现。

  • 对于复杂的状态机来说,代码中充斥着⼤量的 ifelse 或者 switch-case 分⽀判断逻辑,可读性和可维护性差。

    易漏写或者错写某个状态转移。
    如果哪天修改了状态机 中的某个状态转移,我们要在冗⻓的分⽀逻辑中找到对应的代码进⾏修改,很容易改错,导致 bug。
 
查表法
  • 查表法的代码实现更加清晰,可读性和可维护性更好。
  • 当修改 状态机时,只需要修改 transitionTable 和 actionTable 状态转移配置
     
  • 查表法的实现⽅式有⼀定局限性,
    执行的action只能是简单的状态转移操作。

    如果要执⾏的action是⼀系列复杂的逻辑操作(⽐如加减积分、写数据库,还有可能发送消息通知等等),我们就没法⽤如此简单的⼆维数组来表示了。
状态模式
 
  • 对于状态并不多、状态转移也⽐较简单,但事件触发执⾏的action包含的业务逻辑可能⽐较复杂的状态机来说,⾸选状态模式
 
  • 状态模式会引⼊⾮常多的状态类,会导致代码⽐较难维护

像电商下单这种状态并不多,状态转移也⽐较简单,但事件触发执⾏的动作包含的业务逻辑可能会⽐较复杂,更加推荐使⽤状态模式来实现。

像游戏⽐较复杂的状态机,包含的状态⽐较多,优先推荐使⽤查表法;

3.2状态机设计基本原则

  • 明确性:状态和转换必须清晰定义,避免含糊不清的状态
  • 完备性:为所有可能的事件-状态组合定义转换逻辑
  • 可预测性:系统应根据当前状态和给定事件可预测地响应
  • 最⼩化:状态数应保持最⼩,避免不必要的复杂性

3.3状态机常⻅设计误区

  • 过度设计:引⼊不必要的状态和复杂性,使系统难以理解和维护。
  • 不完备的处理:未能处理所有可能的状态转换,导致系统⾏为不确定。
  • 硬编码逻辑:过多的硬编码转换逻辑,使系统不具备灵活性和可扩展性。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号