状态模式的核心是用接口+独立struct替代if-else:每个状态实现State接口,Context持有接口并显式调用TransitionTo切换,状态对象无状态、不持Context,避免隐式修改与并发问题。

状态模式的核心:用类型替代 if-else 分支
Go 没有继承和虚函数,直接照搬传统状态模式容易写成一堆 switch state 或嵌套 if。真正有效的做法是让每个状态成为独立类型,通过接口统一行为,把「判断逻辑」下沉到类型方法里,而不是在业务主流程中反复判断。
关键不是“怎么定义状态”,而是“谁该持有状态变更权”——通常由上下文(Context)持有当前状态接口,所有状态变更都通过 context.TransitionTo(...) 显式触发,避免隐式修改 state 字段导致状态不一致。
用 interface + struct 实现可组合的状态封装
不要用字符串或 int 枚举去标识状态,而应定义状态行为接口,每个具体状态实现它。这样新增状态只需加一个 struct 和对应方法,不改已有逻辑。
-
State接口只暴露业务相关方法(如Handle(ctx *Context)),不暴露内部字段或 setter - 每个状态 struct 不持有
*Context,避免循环引用;需要访问上下文数据时,通过Handle方法传入 - 状态切换由 Context 控制:调用
c.state = newState之前,可做校验(如只允许从IdleState进入RunningState)
type State interface {
Handle(ctx *Context)
}
type IdleState struct{}
func (s *IdleState) Handle(ctx *Context) {
fmt.Println("idle: waiting for start")
ctx.TransitionTo(&RunningState{})
}
type RunningState struct{}
func (s *RunningState) Handle(ctx *Context) {
fmt.Println("running: doing work...")
ctx.TransitionTo(&PausedState{})
}
type Context struct {
state State
}
func (c *Context) TransitionTo(s State) {
c.state = s
}
func (c *Context) Handle() {
c.state.Handle(c)
}
避免常见陷阱:共享状态、隐式转换、漏掉默认分支
很多 Go 实现把状态存在 map 或 switch 中,本质还是条件判断。更隐蔽的问题是:多个 goroutine 并发调用 Handle() 时,c.state 可能被同时修改,造成状态错乱。
立即学习“go语言免费学习笔记(深入)”;
- 状态字段
state必须是State接口类型,不能是具体 struct 指针(否则无法替换) - 如果状态需保存数据(如重试次数、超时时间),应放在
*Context里,而非各状态 struct 中——状态对象应是无状态的(stateless) - 不要在
Handle内部做switch reflect.TypeOf(c.state),这等于又绕回条件判断 - 并发安全需额外处理:
TransitionTo应加sync.Mutex或用atomic.Value替换接口值
何时不该用状态模式?
状态流转简单(只有 2–3 个状态且跳转固定)、或状态逻辑极少变化时,硬套模式反而增加理解成本。比如 HTTP handler 中的请求阶段(parse → validate → execute),用普通函数链式调用更清晰。
真正适合的场景是:状态多(≥5)、跳转关系复杂(如订单生命周期:created → paid → shipped → delivered → refunded)、且不同状态下同一操作语义完全不同(如 Cancel() 在已支付和已发货时行为差异极大)。
这种情况下,把每个状态的 Cancel 行为封进对应 struct 的方法里,比在主逻辑里写七八个 if state == X { ... } else if state == Y { ... } 更易维护,也更容易单测。










