状态模式在Go中应采用组合+接口委托而非继承,通过State接口和上下文字段实现解耦;并发切换需加锁或atomic.Value;数据共享应通过只读方法或显式传参;接口应最小化,仅定义当前状态必需行为。
Go 没有类继承,所以不能照搬 Java/C++ 里“抽象状态类 + 子类实现”的写法。强行用嵌入(embedding)模拟继承,反而会让状态切换逻辑散落在各处、难以追踪。真正符合 Go 风格的做法是:定义 State 接口,让每个具体状态(如 IdleState、RunningState)独立实现该接口;再由上下文结构体(如 Machine)持有一个 State 类型字段,并把行为委托给当前状态对象。
这样做的好处是:状态逻辑彻底解耦,新增状态只需实现接口,不改上下文;切换时只需替换 state 字段值,无副作用。
状态切换常发生在并发场景(比如 goroutine 触发事件),直接赋

m.state = newState 会导致读写冲突。必须加锁或使用原子操作。推荐用 sync.Mutex 包裹状态字段和所有涉及状态的方法调用:
Start()、Pause())内部先 mu.Lock(),调用完再 mu.Unlock()
State 接口方法本身不加锁——它们只负责业务逻辑,不负责同步如果性能敏感且状态切换极少,也可用 atomic.Value 存储 State,但需确保每次赋值都是新实例(因为 atomic.Value 不支持原地修改)。
不同状态可能需要共享上下文数据(如计数器、配置、缓存)。不要让每个 State 实现都持有完整上下文指针——这会破坏封装,也容易引发循环引用。更合理的方式有:
GetCount()、Config()),状态内调用这些方法获取所需信息s.Enter(ctx, lastEvent)),避免隐式依赖id、timeout),可在 State 接口方法签名中作为参数传入,而非从上下文中拉取切忌在 State 实现里直接访问上下文的未导出字段——那等于把封装撕开了一个口子,后续重构成本极高。
State 接口不宜定义太多方法常见错误是把所有可能行为(HandleA、HandleB、OnTimeout、OnError……)全塞进 State 接口。结果是每个具体状态都要实现一堆空方法(或 panic),违反接口最小化原则,也掩盖了真实的行为差异。
更好的做法是:
Run() 在 RunningState 中有意义,在 StoppedState 中应返回错误或忽略)ErrInvalidState 错误,而不是留空实现machine.handleEvent(e)),再根据当前状态调用对应方法——这样能清晰看到“什么事件触发了什么状态行为”接口越小,实现越轻量,测试越容易覆盖边界。状态模式的价值不在“看起来像设计模式”,而在让状态变更路径可读、可测、可推演。