状态模式(State Pattern)
状态模式(State Pattern)
一、模式定义
状态模式允许一个对象在其内部状态改变时改变自身的行为。对象看起来好像修改了自身的类,实际上是把不同状态下的行为委托给不同的状态对象来处理。
一句话理解:同一个方法调用,在不同状态下做完全不同的事情。 就像你的手机——来电时按音量键调铃声音量,听音乐时按音量键调媒体音量,拍照时按音量键当快门。按键没变,行为随状态变了。
二、解决什么问题
- 状态爆炸:一个对象有多个状态,每个状态下的行为不同。如果用 if-else/switch 管理,代码会变成一个巨大的条件分支迷宫
- 状态转换规则复杂:状态之间的跳转有严格约束(A只能到B,B能到C或D,C不能直接到A),分散在各方法中的 if-else 很难维护状态机的一致性
- 开闭原则:新增一个状态时不应该修改已有状态代码
三、核心角色
| 角色 | 职责 |
|---|---|
| State(状态接口) | 声明所有状态共有的行为方法 |
| ConcreteState | 实现特定状态下的行为,负责状态转换逻辑 |
| Context(上下文) | 持有当前状态引用,将行为调用委托给状态对象 |
四、结构图
┌─────────────────────┐ ┌──────────────────────┐
│ Context │ │ State │
├─────────────────────┤ ├──────────────────────┤
│ - state State │──────>│ + HandleRequest() │
│ + Request() │ │ + NextState(ctx) │
│ + SetState(s) │ └──────────┬───────────┘
│ + GetState() State │ │ implements
└─────────────────────┘ ┌────────────┼────────────┐
│ │ │
┌──────┴──┐ ┌──────┴──┐ ┌──────┴──┐
│StateA │ │StateB │ │StateC │
│空闲态 │ │处理态 │ │完成态 │
│Handle() │ │Handle() │ │Handle() │
└─────────┘ └─────────┘ └─────────┘
五、与策略模式的本质区别
虽然结构完全一样,但状态转换的控制权不同:
- 策略模式:由外部调用者(Client)决定用哪个策略,Context 不知道策略之间的存在
- 状态模式:由状态对象自己决定下一个状态,Context 不知道状态转换的细节
这意味着状态类需要持有 Context 的引用(或通过参数传入),才能在行为执行完毕后触发状态切换。
六、完整练习代码
以自动售货机为场景:有投币态、选择商品态、出货态、退货态四个状态。
package main
import "fmt"
// ============================================================
// 前向声明:VendingMachine 结构体
// ============================================================
// VendingMachine 售货机上下文
type VendingMachine struct {
state VendingState
balance int // 当前余额(分)
product string // 选择的商品
price int // 商品价格(分)
stock int // 库存
}
// ============================================================
// 状态接口
// ============================================================
// VendingState 售货机状态接口
type VendingState interface {
// InsertCoin 投币
InsertCoin(vm *VendingMachine, amount int)
// SelectProduct 选择商品
SelectProduct(vm *VendingMachine, product string)
// Dispense 出货
Dispense(vm *VendingMachine)
// Refund 退款
Refund(vm *VendingMachine)
// Name 状态名称
Name() string
}
// ============================================================
// 具体状态实现
// ============================================================
// --- 1. 空闲态(等待投币)---
type IdleState struct{}
func (s *IdleState) Name() string { return "空闲态" }
func (s *IdleState) InsertCoin(vm *VendingMachine, amount int) {
vm.balance += amount
fmt.Printf(" [空闲态] 投入 %d 分,当前余额 %d 分\n", amount, vm.balance)
// 余额大于0,切换到投币态
vm.SetState(&HasCoinState{})
}
func (s *IdleState) SelectProduct(vm *VendingMachine, product string) {
fmt.Println(" [空闲态] 请先投币再选择商品")
}
func (s *IdleState) Dispense(vm *VendingMachine) {
fmt.Println(" [空闲态] 请先投币")
}
func (s *IdleState) Refund(vm *VendingMachine) {
fmt.Println(" [空闲态] 没有余额可退")
}
// --- 2. 已投币态(等待选择商品)---
type HasCoinState struct{}
func (s *HasCoinState) Name() string { return "投币态" }
func (s *HasCoinState) InsertCoin(vm *VendingMachine, amount int) {
vm.balance += amount
fmt.Printf(" [投币态] 再投 %d 分,当前余额 %d 分\n", amount, vm.balance)
}
func (s *HasCoinState) SelectProduct(vm *VendingMachine, product string) {
if vm.stock <= 0 {
fmt.Printf(" [投币态] %s 已售罄,请退款\n", product)
return
}
vm.product = product
fmt.Printf(" [投币态] 选择商品: %s(价格 %d 分)\n", product, vm.price)
// 检查余额是否足够
if vm.balance >= vm.price {
// 余额足够,切换到出货态
vm.SetState(&DispensingState{})
} else {
// 余额不足,提示并保持投币态
fmt.Printf(" [投币态] 余额不足,还差 %d 分\n", vm.price-vm.balance)
}
}
func (s *HasCoinState) Dispense(vm *VendingMachine) {
fmt.Println(" [投币态] 请先选择商品")
}
func (s *HasCoinState) Refund(vm *VendingMachine) {
fmt.Printf(" [投币态] 退款 %d 分\n", vm.balance)
vm.balance = 0
vm.SetState(&IdleState{})
}
// --- 3. 出货态(正在出货)---
type DispensingState struct{}
func (s *DispensingState) Name() string { return "出货态" }
func (s *DispensingState) InsertCoin(vm *VendingMachine, amount int) {
fmt.Println(" [出货态] 正在出货,请勿投币")
}
func (s *DispensingState) SelectProduct(vm *VendingMachine, product string) {
fmt.Println(" [出货态] 正在出货,无法更改商品")
}
func (s *DispensingState) Dispense(vm *VendingMachine) {
vm.stock--
change := vm.balance - vm.price
fmt.Printf(" [出货态] 商品 %s 已出货\n", vm.product)
if change > 0 {
fmt.Printf(" [出货态] 找零 %d 分\n", change)
}
// 重置状态
vm.balance = 0
vm.product = ""
// 如果还有库存,回到空闲态;否则进入售罄态
if vm.stock > 0 {
vm.SetState(&IdleState{})
} else {
vm.SetState(&SoldOutState{})
}
}
func (s *DispensingState) Refund(vm *VendingMachine) {
fmt.Println(" [出货态] 正在出货,无法退款")
}
// --- 4. 售罄态 ---
type SoldOutState struct{}
func (s *SoldOutState) Name() string { return "售罄态" }
func (s *SoldOutState) InsertCoin(vm *VendingMachine, amount int) {
fmt.Println(" [售罄态] 商品已售罄,拒绝投币")
}
func (s *SoldOutState) SelectProduct(vm *VendingMachine, product string) {
fmt.Println(" [售罄态] 商品已售罄")
}
func (s *SoldOutState) Dispense(vm *VendingMachine) {
fmt.Println(" [售罄态] 无货可出")
}
func (s *SoldOutState) Refund(vm *VendingMachine) {
if vm.balance > 0 {
fmt.Printf(" [售罄态] 退款 %d 分\n", vm.balance)
vm.balance = 0
}
vm.SetState(&IdleState{})
}
// ============================================================
// Context 方法实现
// ============================================================
func NewVendingMachine(price, stock int) *VendingMachine {
return &VendingMachine{
state: &IdleState{},
price: price,
stock: stock,
balance: 0,
}
}
func (vm *VendingMachine) SetState(s VendingState) {
fmt.Printf(" 状态切换: %s -> %s\n", vm.state.Name(), s.Name())
vm.state = s
}
func (vm *VendingMachine) GetState() VendingState {
return vm.state
}
func (vm *VendingMachine) InsertCoin(amount int) {
vm.state.InsertCoin(vm, amount)
}
func (vm *VendingMachine) SelectProduct(product string) {
vm.state.SelectProduct(vm, product)
}
func (vm *VendingMachine) Dispense() {
vm.state.Dispense(vm)
}
func (vm *VendingMachine) Refund() {
vm.state.Refund(vm)
}
// ============================================================
// 主函数:演示售货机状态流转
// ============================================================
func main() {
// 创建售货机:商品价格200分(2元),库存2件
vm := NewVendingMachine(200, 2)
fmt.Println("===== 场景1: 正常购买 =====")
fmt.Println("当前状态:", vm.GetState().Name())
vm.InsertCoin(100) // 投入1元
vm.InsertCoin(100) // 再投1元
vm.SelectProduct("可乐") // 选择商品
vm.Dispense() // 出货
fmt.Println("\n===== 场景2: 余额不足再补投 =====")
fmt.Println("当前状态:", vm.GetState().Name())
vm.InsertCoin(100) // 投入1元(余额100,不够)
vm.SelectProduct("可乐") // 余额不足提示
vm.InsertCoin(100) // 再投1元(余额200,够了)
vm.SelectProduct("可乐") // 选择商品
vm.Dispense() // 出货(库存→0,进入售罄态)
fmt.Println("\n===== 场景3: 售罄后尝试购买 =====")
fmt.Println("当前状态:", vm.GetState().Name())
vm.InsertCoin(200) // 售罄态拒绝投币
vm.SelectProduct("可乐") // 售罄态拒绝选择
vm.Refund() // 无余额可退,回到空闲态
fmt.Println("最终状态:", vm.GetState().Name())
}
七、运行结果
===== 场景1: 正常购买 =====
当前状态: 空闲态
[空闲态] 投入 100 分,当前余额 100 分
状态切换: 空闲态 -> 投币态
[投币态] 再投 100 分,当前余额 200 分
[投币态] 选择商品: 可乐(价格 200 分)
状态切换: 投币态 -> 出货态
[出货态] 商品 可乐 已出货
状态切换: 出货态 -> 空闲态
===== 场景2: 余额不足再补投 =====
当前状态: 空闲态
[空闲态] 投入 100 分,当前余额 100 分
状态切换: 空闲态 -> 投币态
[投币态] 选择商品: 可乐(价格 200 分)
[投币态] 余额不足,还差 100 分
[投币态] 再投 100 分,当前余额 200 分
[投币态] 选择商品: 可乐(价格 200 分)
状态切换: 投币态 -> 出货态
[出货态] 商品 可乐 已出货
状态切换: 出货态 -> 售罄态
===== 场景3: 售罄后尝试购买 =====
当前状态: 售罄态
[售罄态] 商品已售罄,拒绝投币
[售罄态] 商品已售罄
[售罄态] 没有余额可退
状态切换: 售罄态 -> 空闲态
最终状态: 空闲态
三个场景串联了完整的状态流转链:空闲态 → 投币态 → 出货态 → 空闲态/售罄态 → 空闲态。
八、关键设计决策解析
1. 状态对象是否需要持有 Context 引用
在本实现中,状态对象通过方法参数接收 *VendingMachine,而不是在构造时保存引用。好处是状态对象可以是无状态的(IdleState、HasCoinState 等都是零字段结构体),可以复用单例实例。
如果状态对象需要维护自己的数据(比如记录进入该状态的时间),则可以在构造时传入 Context 引用。
2. 状态转换放在哪里
两种风格:
- 状态类内部转换(本实现):状态类在 Handle 方法中调用
vm.SetState()切换状态。优点是状态转换规则集中在一处,易于理解状态机 - Context 中集中转换:Context 在调用
state.Handle()后根据返回值决定下一个状态。优点是状态类更纯粹,缺点是 Context 变胖
实际项目中,简单状态机用第一种更直观;复杂状态机建议用状态转换表(状态+事件→下一状态的映射表)。
3. 用 map 实现状态转换表
当状态和事件多到一定程度时,可以用矩阵式转换表替代每个状态类中的 if-else:
// transitionTable[当前状态][事件] = 下一个状态
var transitionTable = map[string]map[string]string{
"空闲态": {"投币": "投币态", "选择": "空闲态", "出货": "空闲态", "退款": "空闲态"},
"投币态": {"投币": "投币态", "选择足够": "出货态", "选择不足": "投币态", "出货": "投币态", "退款": "空闲态"},
// ...
}
这种方式把转换规则从代码中抽离到数据结构中,新增状态只需改表不改代码,真正实现开闭原则。
九、实际项目身影
- TCP 连接状态机:LISTEN → SYN_SENT → ESTABLISHED → FIN_WAIT → CLOSED,每个状态下对报文的处理完全不同
- HTTP 请求生命周期:Idle → Reading → Processing → Writing → Closed
- 游戏角色状态:站立/跑/跳/攻击/受伤,同一输入在不同状态下触发不同动作
- Go 的
database/sql连接池:连接有 free/in-use/expired 等状态,不同状态下获取/归还行为不同
十、小结
状态模式的核心价值是把状态驱动的条件分支转化为多态分发。当你发现一个对象有大量 if state == X { ... } else if state == Y { ... } 的代码,而且每个分支都很长时,就是状态模式上场的信号。Go 中通过接口+结构体的组合,可以非常优雅地实现状态机,且状态对象可以设计为零字段的轻量单例。

浙公网安备 33010602011771号