状态模式:让对象"变身"
你有没有注意过手机的行为会随着"状态"变化?静音模式下来电不响铃,飞行模式下无法上网。同一部手机,不同状态下表现完全不同。这就是状态模式的核心思想。
状态模式 让一个对象在内部状态改变时,改变它的行为。看起来就像对象换了一个类一样。
为什么需要状态模式?
假设你在开发一个自动售货机,它有多种状态:
// ❌ 糟糕的写法:用 if-else 处理所有状态
func (m *VendingMachine) InsertMoney() {
if m.state == "NO_ITEM" {
fmt.Println("没货,退钱")
} else if m.state == "HAS_ITEM" {
fmt.Println("请先选择商品")
} else if m.state == "ITEM_SELECTED" {
fmt.Println("收到钱,出货")
m.state = "HAS_MONEY"
} else if m.state == "HAS_MONEY" {
fmt.Println("已收过钱了")
}
// 每个方法都要这样写一遍...
}
这样写的问题:
- 代码臃肿:每个方法都有大量 if-else
- 难以维护:新增状态要改所有方法
- 状态转换不清晰:很难看出完整的状态流转图
状态模式的解法: 把每种状态封装成独立的类,让状态类自己决定如何响应操作 。
模式结构
| 角色 | 职责 | 类比 |
|---|---|---|
| Context(上下文) | 持有当前状态对象,把请求委托给状态处理 | 售货机本体 |
| State(状态接口) | 定义所有状态共有的行为 | 售货机的操作面板 |
| ConcreteState(具体状态) | 实现特定状态下的行为 | 各种状态:有货、无货、已投币... |
动手实现:自动售货机
用自动售货机来演示状态模式。售货机有四种状态: 有货 → 已选商品 → 已投币 → 出货 。
第一步:定义状态接口
第二步:实现各种具体状态
package main
import "fmt"
// HasItemState 有货状态
type HasItemState struct {
machine *VendingMachine
}
func (s *HasItemState) SelectItem() error {
fmt.Println("🛒 商品已选择")
s.machine.SetState(s.machine.itemSelected)
return nil
}
func (s *HasItemState) InsertMoney() error {
return fmt.Errorf("❌ 请先选择商品")
}
func (s *HasItemState) Dispense() error {
return fmt.Errorf("❌ 请先选择商品并付款")
}
func (s *HasItemState) AddItem() error {
fmt.Println("📦 补货成功")
s.machine.itemCount++
return nil
}
package main
import "fmt"
// ItemSelectedState 已选商品状态
type ItemSelectedState struct {
machine *VendingMachine
}
func (s *ItemSelectedState) SelectItem() error {
return fmt.Errorf("❌ 已选择商品,请投币")
}
func (s *ItemSelectedState) InsertMoney() error {
fmt.Println("💰 收到付款")
s.machine.SetState(s.machine.hasMoney)
return nil
}
func (s *ItemSelectedState) Dispense() error {
return fmt.Errorf("❌ 请先付款")
}
func (s *ItemSelectedState) AddItem() error {
return fmt.Errorf("❌ 交易进行中,无法补货")
}
package main
import "fmt"
// HasMoneyState 已投币状态
type HasMoneyState struct {
machine *VendingMachine
}
func (s *HasMoneyState) SelectItem() error {
return fmt.Errorf("❌ 已付款,正在出货")
}
func (s *HasMoneyState) InsertMoney() error {
return fmt.Errorf("❌ 已付过款")
}
func (s *HasMoneyState) Dispense() error {
fmt.Println("🎁 出货中...")
s.machine.itemCount--
if s.machine.itemCount == 0 {
fmt.Println("⚠️ 商品已售罄")
s.machine.SetState(s.machine.noItem)
} else {
s.machine.SetState(s.machine.hasItem)
}
return nil
}
func (s *HasMoneyState) AddItem() error {
return fmt.Errorf("❌ 交易进行中,无法补货")
}
package main
import "fmt"
// NoItemState 无货状态
type NoItemState struct {
machine *VendingMachine
}
func (s *NoItemState) SelectItem() error {
return fmt.Errorf("❌ 商品已售罄")
}
func (s *NoItemState) InsertMoney() error {
return fmt.Errorf("❌ 商品已售罄,无法付款")
}
func (s *NoItemState) Dispense() error {
return fmt.Errorf("❌ 无商品可出")
}
func (s *NoItemState) AddItem() error {
fmt.Println("📦 补货成功")
s.machine.itemCount++
s.machine.SetState(s.machine.hasItem)
return nil
}
第三步:实现上下文(售货机)
package main
import "fmt"
// VendingMachine 自动售货机
type VendingMachine struct {
hasItem State
itemSelected State
hasMoney State
noItem State
currentState State
itemCount int
}
// NewVendingMachine 创建售货机
func NewVendingMachine(itemCount int) *VendingMachine {
m := &VendingMachine{itemCount: itemCount}
// 初始化所有状态
m.hasItem = &HasItemState{machine: m}
m.itemSelected = &ItemSelectedState{machine: m}
m.hasMoney = &HasMoneyState{machine: m}
m.noItem = &NoItemState{machine: m}
// 设置初始状态
if itemCount > 0 {
m.currentState = m.hasItem
} else {
m.currentState = m.noItem
}
return m
}
func (m *VendingMachine) SetState(s State) {
m.currentState = s
}
func (m *VendingMachine) SelectItem() error {
return m.currentState.SelectItem()
}
func (m *VendingMachine) InsertMoney() error {
return m.currentState.InsertMoney()
}
func (m *VendingMachine) Dispense() error {
return m.currentState.Dispense()
}
func (m *VendingMachine) AddItem() error {
return m.currentState.AddItem()
}
func (m *VendingMachine) PrintStatus() {
fmt.Printf("📊 库存: %d 件\n", m.itemCount)
}
第四步:使用售货机
package main
import "fmt"
func main() {
// 创建售货机,初始库存 1 件
machine := NewVendingMachine(1)
machine.PrintStatus()
fmt.Println("\n=== 正常购买流程 ===")
machine.SelectItem() // 选择商品
machine.InsertMoney() // 投币
machine.Dispense() // 出货
machine.PrintStatus()
fmt.Println("\n=== 售罄后尝试购买 ===")
err := machine.SelectItem()
if err != nil {
fmt.Println(err)
}
fmt.Println("\n=== 补货后继续购买 ===")
machine.AddItem()
machine.SelectItem()
machine.InsertMoney()
machine.Dispense()
}
状态模式 vs 策略模式
这两个模式结构相似,但意图不同:
| 特性 | 状态模式 | 策略模式 |
|---|---|---|
| 切换时机 | 自动切换(内部触发) | 手动切换(外部指定) |
| 状态之间 | 知道彼此存在,可以互相切换 | 互不知道,完全独立 |
| 典型场景 | 有限状态机、工作流 | 算法选择、行为参数化 |
什么时候该用状态模式?
| 场景 | 说明 |
|---|---|
| 对象行为依赖状态 | 不同状态下同一操作有不同表现 |
| 状态数量多 | 大量条件分支判断当前状态 |
| 状态转换逻辑复杂 | 需要清晰的状态转换图 |
常见应用 :
- 订单系统:待支付 → 已支付 → 已发货 → 已收货
- 游戏角色:正常 → 受伤 → 死亡
- TCP 连接:监听 → 已建立 → 关闭
- 文档审批:草稿 → 审核中 → 已批准/已拒绝
优缺点分析
| ✅ 优点 | ❌ 缺点 |
|---|---|
| 消除条件分支:每个状态类职责单一 | 类数量增加:每个状态一个类 |
| 开闭原则:新增状态无需修改现有代码 | 状态少时过度设计:简单情况用 if-else 更直接 |
| 状态转换清晰:每个状态知道下一个状态是谁 |
与其他模式的关系
| 模式组合 | 说明 |
|---|---|
| 状态 vs 策略 | 策略是"怎么做",状态是"现在是什么情况" |
| 状态 + 单例 | 状态对象通常可以做成单例 |
| 状态 + 享元 | 共享状态对象以节省内存 |
一句话总结:状态模式就像变形金刚——同一个机器人,在不同模式下有完全不同的形态和能力。
