直接写switch-case不算真正的状态机,因其缺乏显式状态转移约束和合法性校验,无法在编译期或初始化时验证转移是否合法,易导致非法跳转引发运行时错误。
为什么直接写 switch-case 不算真正的状态机
很多人用 switch 判断 state 变量然后调用不同逻辑,这看似是状态机,但缺乏状态转移的显式约束和合法性校验。比如从 StateIdle 直接跳到 StateFailed 而不经过 StateRunning,代码里可能完全没拦住——运行时出问题才暴露。真正的状态机需要把「哪些状态能到哪些状态」提前定义清楚,最好在编译期或初始化时就校验。
用 map[State]map[Event]State 定义转移规则最实用
比手写一堆 if/switch 更可维护,也方便做合法性检查。核心结构通常是:
type State uint8
type Event uint8
var transitions = map[State]map[Event]State{
StateIdle: {
EventStart: StateRunning,
},
StateRunning: {
EventSuccess: StateDone,
EventFail: StateFailed,
},
}
使用时只需查表:next, ok := transitions[current][event]。如果 !ok,说明这个事件当前状态下不允许触发——直接返回错误或 panic,而不是静默忽略。
- 转移表建议在
init()里做完整性检查(比如每个状态是否至少有一个出边) - 避免用字符串做
State或Event,类型安全差,拼错难发现 - 如果事件携带数据,不要塞进转移表,而是让事件处理器(handler)自己解析
State 接口 + 嵌入式行为组合比继承更 Go 风格
Go 没有继承,但可以用接口 + 组合实现状态行为解耦。定义一个 State 接口: