朱日和基地的演练场,打法变了。红军和蓝军的对抗,不再只是人与人的较量。红军用上了电子压制;蓝军则利用地形进行战术周旋。战场的主导权,正在悄悄转移……从...
2025-07-28 0
作为一名后端开发工程师,当你面对复杂的业务流程时,是否常感到逻辑混乱、边界不清?学会状态机设计,让你的代码优雅如诗!
在后台系统开发中,我们经常需要处理对象的状态流转问题:订单从"待支付"到"已支付"再到"已发货",工单系统从"打开"到"处理中"再到"解决",这些场景都涉及 状态管理 。
如果不使用状态机设计,我们可能会写出这样的面条式代码:
func HandleOrderEvent(order *Order, event Event) error { if order.Status == "待支付" { if event.Type == "支付成功" { order.Status = "已支付" } else if event.Type == "取消订单" { order.Status = "已取消" #技术分享 } else { return errors.New("非法事件") } } else if order.Status == "已支付" { if event.Type == "发货" { order.Status = "已发货" } }}
这种代码存在几个致命问题:
状态机正是解决这类问题的银弹!
| 概念 | 描述 | 订单系统示例 | | ---
| 状态(State) | 系统所处的稳定状态 | 待支付、已支付、已发货 | | 事件(Event) | 触发状态变化的动作 | 支付成功、取消订单 | | 转移(Transition) | 状态变化的规则 | 待支付 → 已支付 |
graph LR A[待支付] -->|支付成功| B[已支付] B -->|发货| C[已发货] B -->|申请退款| D[退款中] A -->|取消订单| E[已取消] D -->|退款成功| E D -->|退款失败| B
package mainimport "fmt"type State stringtype Event stringtype TransitionHandler func() errortype Transition struct { From State Event Event To State Handle TransitionHandler }type StateMachine struct { Current State transitions []Transition }func (sm *StateMachine) AddTransition(from State, event Event, to State, handler TransitionHandler) { sm.transitions = append(sm.transitions, Transition{ From: from, Event: event, To: to, Handle: handler, }) }func (sm *StateMachine) Trigger(event Event) error { for _, trans := range sm.transitions { if trans.From == sm.Current && trans.Event == event { if err := trans.Handle(); err != nil { return err } sm.Current = trans.To return nil } } return fmt.Errorf("非法事件[%s]或当前状态[%s]不支持", event, sm.Current) }
const ( StatePending State = "待支付" StatePaid State = "已支付" StateShipped State = "已发货" StateCanceled State = "已取消")const ( EventPaySuccess Event = "支付成功" EventCancel Event = "取消订单" EventShip Event = "发货" )func main() { sm := &StateMachine{Current: StatePending} sm.AddTransition(StatePending, EventPaySuccess, StatePaid, func() error { fmt.Println("执行支付成功处理逻辑...") return nil }) sm.AddTransition(StatePending, EventCancel, StateCanceled, func() error { fmt.Println("执行订单取消逻辑...") return nil }) sm.AddTransition(StatePaid, EventShip, StateShipped, func() error { fmt.Println("执行发货逻辑...") return nil }) sm.AddTransition(StatePaid, EventCancel, StateCanceled, func() error { fmt.Println("执行已支付状态的取消逻辑...") return nil }) fmt.Println("当前状态:", sm.Current) _ = sm.Trigger(EventPaySuccess) fmt.Println("当前状态:", sm.Current) _ = sm.Trigger(EventShip) fmt.Println("当前状态:", sm.Current) err := sm.Trigger(EventCancel) fmt.Println("尝试取消:", err) }
输出结果:
当前状态: 待支付执行支付成功处理逻辑...当前状态: 已支付执行发货逻辑...当前状态: 已发货尝试取消: 非法事件[取消订单]或当前状态[已发货]不支持
上面的实现足够清晰,但存在性能问题——每次触发事件都需要遍历转移表。我们优化为更高效的版本:
type StateMachineV2 struct { Current State transitionMap map[State]map[Event]*Transition}func (sm *StateMachineV2) AddTransition(from State, event Event, to State, handler TransitionHandler) { if sm.transitionMap == nil { sm.transitionMap = make(map[State]map[Event]*Transition) } if _, exists := sm.transitionMap[from]; !exists { sm.transitionMap[from] = make(map[Event]*Transition) } sm.transitionMap[from][event] = &Transition{ From: from, Event: event, To: to, Handle: handler, } }func (sm *StateMachineV2) Trigger(event Event) error { if events, exists := sm.transitionMap[sm.Current]; exists { if trans, exists := events[event]; exists { if err := trans.Handle(); err != nil { return err } sm.Current = trans.To return nil } } return fmt.Errorf("非法事件[%s]或当前状态[%s]不支持", event, sm.Current) }
绘制状态转移图,与代码实现保持同步:
使用 Go 的接口特性实现面向对象的状态模式:
type OrderState interface { Pay() error Cancel() error Ship() error}type pendingState struct{}func (s *pendingState) Pay() error { fmt.Println("执行支付成功处理逻辑...") return nil }func (s *pendingState) Cancel() error { fmt.Println("执行待支付状态取消逻辑...") return nil }func (s *pendingState) Ship() error { return errors.New("当前状态不能发货") }type Order struct { state OrderState }func (o *Order) ChangeState(state OrderState) { o.state = state }func (o *Order) Pay() error { return o.state.Pay() }
如何在数据库中存储状态机?永远只存储状态,而不是存储状态机逻辑!
数据库表设计示例:
| 字段名 | 类型 | 描述 | | ---
| id | int | 主键 ID | | status | varchar(20) | 当前状态 | | event_history | json | 事件历史记录 |
状态恢复代码实现:
type Order struct { ID int Status State}func RecoverOrderStateMachine(order Order) *StateMachine { sm := CreateStateMachine() sm.Current = order.Status return sm }
var mutex sync.Mutexfunc (sm *StateMachine) SafeTrigger(event Event) error { mutex.Lock() defer mutex.Unlock() return sm.Trigger(event) }func (sm *StateMachine) AsyncTrigger(event Event) error { eventChan := make(chan error) go func() { mutex.Lock() defer mutex.Unlock() eventChan <- sm.Trigger(event) }() return <-eventChan }
监控状态转移示例:
func (sm *StateMachine) Trigger(event Event) error { startTime := time.Now() defer func() { log.Printf("状态转移监控: %s->%s (%s) 耗时: %v", oldState, sm.Current, event, time.Since(startTime)) }()}
状态机不只是解决业务逻辑的工具,它更是一种思维方式。通过今天的学习,你应该掌握了:
当你在设计下一个后端系统时,先问自己三个问题:
思考清楚这些问题,你的代码设计将变得更加清晰优雅!
相关文章
朱日和基地的演练场,打法变了。红军和蓝军的对抗,不再只是人与人的较量。红军用上了电子压制;蓝军则利用地形进行战术周旋。战场的主导权,正在悄悄转移……从...
2025-07-28 0
说起默克尔,这位德国前总理可不是随便说说的人。她在位的时候,就经常提醒大家,欧洲要是卷入俄乌冲突,得付出大代价。很多人当时觉得她太小心了,现在回想起来...
2025-07-28 0
在阅读此文之前,麻烦您点击一下“关注”,既方便您进行讨论和分享,又能给您带来不一样的参与感,感谢您的支持文|长屿编辑|小娄要说现在的高危职业有哪些?网...
2025-07-28 0
中国在雅鲁藏布江下游超大型水电工程刚动工,莫迪政府还没有回应,印媒就先鼓噪起来了。印度主持人甲亢姐更是叫嚣,要出动飞机炸毁中国工地,不仅失去基本理智,...
2025-07-28 0
前言:手机续航已经成为各厂家展示肌肉的另一主场,目前荣耀已发布迄今为止最大容量8000毫安的荣耀power手机,而据iQOO的产品经理和知名数码博主爆...
2025-07-28 0
北京市科学技术委员会、中关村科技园区管理委员会关于公开征集2025年人工智能颠覆性技术方向储备课题的通知为落实《北京市加快建设具有全球影响力的人工智能...
2025-07-28 0
7月25日,湖南航天医院急诊科主任陈金文在微信朋友圈刷到一条暖心的新闻,才得知该院护理部主任、副主任护师刘爱军几天前在飞机上挺身救人的事迹。同事和朋友...
2025-07-28 0
大多数人买完手机后,都会选择买一个心仪的手机壳,用来套在手机上,保护手机。这样,就算不小心将手机摔到地上后,手机也不会有损伤的。但是,对于大多数人来说...
2025-07-28 0
发表评论