大家好,我是十三!欢迎来到十三Tech。

订单有五种状态——待支付、已支付、已发货、已签收、已取消。每种状态下能做的事不同:待支付只能支付或取消,已支付只能发货或退款,已发货只能签收。最初的代码在每个方法里 switch state 判断——Pay 方法里五种状态判断一遍、Ship 方法里又一遍、Cancel 方法里再来一遍。改一个状态分支要改四个方法。

后来用状态模式——每种状态封装成独立的 State 对象,订单委托给当前 state 处理。PayPendingState 里执行支付、在 PaidState 里返回"已支付不能重复支付"。条件分支消失了,状态转换在 state 内部完成

状态和策略长得最像——结构一模一样,但意图截然不同。这一篇就聊聊状态的标志特征是"状态自己切换状态",以及它和策略的真实差别。

状态的现实类比:售货机投币前后按钮行为不同

一、状态在解决什么——不是"行为变化"

教科书定义:允许对象在内部状态改变时改变其行为。这话模糊——策略模式也可以"改变行为"。真正的状态有一个标志特征——状态自己切换状态

策略是"客户端从外面切换算法",状态是"状态对象在内部触发状态切换"。订单调用 Pay() 时,PendingState.Pay() 内部把订单的状态改成 PaidState——下一次调 Ship() 走的就是 PaidState 的逻辑。这个"内部自动切换"是状态区别于策略的核心。

策略换一次算法,下次还是同一个客户端在选;状态换一次,下次走哪个 state 取决于上一次操作。状态有"记忆",策略没有

判断口诀:行为依赖历史 + 状态间有转换规则 → 状态;客户端临时选算法 → 策略

二、状态模式怎么写——Context 委托给当前 State

状态模式的核心是 Context 持有当前 State,所有操作委托给 State。State 实现具体行为,必要时切换 Context 的状态。

状态 UML 结构:Context 委托给当前 State

type Order struct {
    ID    string
    state State
}

func (o *Order) SetState(s State) { o.state = s }
func (o *Order) Pay()  error { return o.state.Pay(o) }
func (o *Order) Ship() error { return o.state.Ship(o) }

type State interface {
    Pay(o *Order) error
    Ship(o *Order) error
}

type PendingState struct{}
func (s *PendingState) Pay(o *Order) error {
    o.SetState(&PaidState{}); return nil
}
func (s *PendingState) Ship(o *Order) error { return ErrInvalidOp }

PendingState.Pay——执行支付逻辑后,主动调 o.SetState(&PaidState{}) 切换状态。下次 Ship() 走的就是 PaidState.Ship 的逻辑。这个"内部切换"是状态模式的核心动作。

写状态模式有个工程坑:状态对象尽量无状态PendingState 不应该持有 Order 的数据——它只是一个"行为集合"。ConcreteState 通常单例化(享元模式),所有 Order 共享同一份 PendingState 实例。如果 ConcreteState 持有 Order 数据,对象数量爆炸。

状态调用流程:Pay 触发 Pending → Paid 状态转换

三、什么时候别用状态模式

状态模式不是所有"有状态"场景都适用,至少有三个反模式。

反模式一:只有 2 个状态。两个状态用布尔字段(isActive)就够了,写两个 State 类是过度设计。状态模式只有状态多(≥3)且每状态下行为差异大时才合适。

反模式二:状态判断本身简单。如果状态判断就是 if s == "paid" { ... }——直接写在业务方法里。状态模式的价值是把"状态判断"从业务方法里剥离——剥离的成本(State 接口、ConcreteState 类)只有判断逻辑复杂时才摊得平。

反模式三:状态转换无规则。状态模式假设状态转换有约束(不能从"已签收"跳到"待支付")。如果状态可以任意转换(比如配置项),状态模式的约束反而成了枷锁,用简单的枚举+switch 更灵活。

状态适用决策:状态多 + 状态间行为差异大

Go 标准库里状态模式最典型的地方是 net/http.Server.ConnState——HTTP server 把每条连接的状态变化抽象成回调(New、Active、Idle、Hijacked、Closed)。server 内部维护连接状态机,不同状态下行为不同(Idle 连接可被复用、Closed 连接要释放资源)。看 net/http 的 server 源码,连接状态机是一个教科书级的状态模式实现。

回头看那个订单状态机——值得用状态模式吗?值得,五种状态、每状态下四五个操作、状态转换规则复杂,正是状态模式的甜区。但如果只是"开/关"两个状态,布尔字段更直接。

状态模式真正教会我的,不是"行为随状态切换",而是把"状态"从"操作"里剥离——状态是稳定的、操作是多变的,两者分开后改动一处不影响另一处。这个分离原则比模式本身重要得多。

下一篇讲命令模式,它把"请求"封装成对象,让请求可排队、可撤销、可日志。


关于十三Tech 资深服务端研发,AI 实践者,专注分享真实可落地的技术经验。 相信 AI 是程序员的最佳搭档。

联系方式:569893882@qq.com GitHub:@TriTechAI