Go中状态模式不用类继承是因为其无继承机制,应通过interface定义行为、结构体字段存状态、方法委托或函数值切换逻辑,核心是明确事件响应责任。

状态模式在 Go 里为什么不用类继承
Go 没有继承,所以没法照搬 Java/C++ 那套「抽象状态类 + 具体子类」的写法。强行模拟只会让代码变重、接口膨胀,还容易绕晕自己。
实际做法是用 interface 定义行为契约,用结构体字段存当前状态,再靠方法委托或函数值切换逻辑。核心不是“谁是状态”,而是“谁负责响应事件”。
- 状态行为应尽量无副作用,纯函数式处理更易测试
- 避免在状态方法里直接修改
state字段——应该由统一的Transition或Handle方法控制流转 - 别把所有状态逻辑塞进一个大 switch;拆成独立函数或小结构体,按需组合
用 map[string]func() 实现轻量 FSM 的坑
有人图快,直接用 map[string]func() 存事件处理器,比如:handlers["click"] = func() { s.state = "active" }。短期能跑,但很快会失控。
问题在于:状态变更和业务逻辑混在一起,无法校验转移合法性(比如从 "idle" 直接到 "error"),也没法拦截非法事件。
立即学习“go语言免费学习笔记(深入)”;
- 必须加一层状态合法性检查,例如
if !s.isValidTransition("click") { return errors.New("invalid transition") } - 事件处理函数最好返回
error,而不是 panic —— FSM 常用于网络协议或设备驱动,错误得可恢复 -
map键名建议用常量定义,如const EventClick = "click",避免字符串散落各处
推荐用 fsm 包时怎么避免 runtime panic
第三方库 github.com/looplab/fsm 是最常用的 Go FSM 实现,但它默认不校验状态机定义是否完备,运行时报 panic: invalid transition 很常见。
根本原因是初始化时没传全 Events 和 Callbacks,或者状态名拼错、大小写不一致(Go 区分大小写,"Idle" ≠ "idle")。
- 初始化前先调用
fsm.Check(),它会返回缺失的转移路径列表 - 所有状态名、事件名统一用小写 + 下划线,比如
"waiting_for_ack",减少命名歧义 - 别依赖
OnTransition做关键状态检查——它只在成功转移后触发;关键校验要放在OnBeforeEvent里
状态持久化时如何避免状态不一致
FSM 用在订单、设备控制等场景时,常需把当前状态存到数据库或 Redis。但写状态和发事件不是原子操作,网络延迟、进程崩溃都可能导致状态卡住。
典型现象是日志显示事件已处理,但 DB 里状态还是旧的,后续事件被拒绝。
- 先持久化状态,再触发事件处理;失败则中断,不重试未确认的状态变更
- 加版本号或时间戳字段,防止并发写覆盖,比如用
UPDATE states SET state=?, version=? WHERE id=? AND version=? - 启动时做一次状态自检:读 DB 状态,对比 FSM 内部状态,不一致就重建或报警
状态模式最难的不是定义状态,而是守住边界——什么时候允许跳转、谁有权发起、失败后怎么兜底。这些细节不写进代码,光靠文档没人记得住。










