代码界的川剧大师:状态模式的变脸艺术
一、当对象开始「精分」
你是否见过这样的戏精代码?
自动售货机收钱时是舔狗,出货时是高冷御姐;
游戏角色满血时勇如吕布,残血时怂似苟王;
电梯运行时六亲不认,故障时秒变贴心客服...
这就是状态模式的魔力——让对象像川剧演员般瞬间变脸,每个状态都是独立人格,却共享同一副躯壳!
二、影帝的表演手册(UML图)
┌─────────────┐ ┌─────────────┐
│ Context │<>───────>│ State │
├─────────────┤ ├─────────────┤
│ +request() │ │ +handle() │
└──────△──────┘ └──────△──────┘
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ Concrete │ │ Concrete │
│ Context │ │ StateA │
└─────────────┘ └─────────────┘
- 导演(Context):持有当前状态的戏精本体
- 剧本大纲(State):定义所有表演套路
- 分镜脚本(ConcreteState):具体人格的表演细节
三、程序界的奥斯卡现场(代码实战)
1. 创建戏精本体(自动售货机)
public class VendingMachine {
private State state; // 当前人格
private int stock = 5; // 库存
// 初始化待机人格
public VendingMachine() {
this.state = new IdleState();
}
// 切换人格的方法
void changeState(State state) {
this.state = state;
}
// 以下是用户可能触发的操作
public void insertCoin() {
state.insertCoin(this);
}
public void selectItem() {
state.selectItem(this);
}
public void dispense() {
state.dispense(this);
stock--;
}
}
2. 编写表演剧本(状态接口)
interface State {
void insertCoin(VendingMachine vm);
void selectItem(VendingMachine vm);
void dispense(VendingMachine vm);
}
3. 具体人格演绎
// 待机状态(高冷人设)
class IdleState implements State {
public void insertCoin(VendingMachine vm) {
System.out.println("💰 硬币已投入");
vm.changeState(new HasCoinState());
}
public void selectItem(VendingMachine vm) {
System.out.println("❌ 请先投币");
}
public void dispense(VendingMachine vm) {
System.out.println("❌ 请先选择商品");
}
}
// 已投币状态(舔狗模式)
class HasCoinState implements State {
public void insertCoin(VendingMachine vm) {
System.out.println("❌ 已投币,别塞了");
}
public void selectItem(VendingMachine vm) {
System.out.println("✅ 商品已选择");
vm.changeState(new DispensingState());
}
public void dispense(VendingMachine vm) {
System.out.println("❌ 请先选择商品");
}
}
// 出货状态(工作狂人格)
class DispensingState implements State {
public void insertCoin(VendingMachine vm) {
System.out.println("❌ 出货中请勿投币");
}
public void selectItem(VendingMachine vm) {
System.out.println("❌ 出货中请勿选择");
}
public void dispense(VendingMachine vm) {
if(vm.stock > 0) {
System.out.println("🎉 商品掉落口");
vm.changeState(new IdleState());
} else {
System.out.println("⚠️ 库存不足");
vm.changeState(new SoldOutState());
}
}
}
四、状态模式 vs 策略模式:演员与军师的区别
| 维度 | 状态模式 | 策略模式 |
|---|---|---|
| 目的 | 管理状态驱动的行为变化 | 灵活替换算法 |
| 状态感知 | 各状态知道下一个状态 | 策略间无关联 |
| 自主权 | 状态可自行切换 | 由外部指定策略 |
| 典型应用 | 订单状态流转 | 支付方式选择 |
| 现实类比 | 人格分裂 | 锦囊妙计 |
五、代码影帝的片场实录
-
电商订单系统:
- 待付款 → 已付款 → 发货中 → 已完成
- 每个状态限制不同操作(不能发货未付款订单)
-
游戏引擎:
- 角色状态:站立/奔跑/跳跃/受伤
- NPC状态:巡逻/追击/攻击/逃跑
-
交通信号灯:
- 红灯停 → 绿灯行 → 黄灯减速
-
工单系统:
- 待处理 → 处理中 → 已解决 → 已关闭
-
播放器控制:
- 停止 → 播放 → 暂停 → 快进
冷知识:
TCP协议的状态机(SYN-SENT、ESTABLISHED等)是状态模式的网络级应用!
六、防NG拍摄指南
-
避免上帝状态:
// 错误示范:在Context中写满if-else状态判断 public void handle() { if (state == A) { ... } else if (state == B) { ... } } -
状态转换规范化:
// 正确做法:把转换逻辑放在状态类中 class StateA implements State { void handle(Context ctx) { // 处理逻辑 ctx.changeState(new StateB()); } } -
警惕状态爆炸:
当状态超过10个时,考虑: 1. 合并相似状态 2. 引入状态层级 3. 改用状态表驱动 -
共享状态对象:
// 无内部状态的状态类可以共享实例 State idle = IdleState.INSTANCE; -
异步状态处理:
// 复杂状态转换使用线程池处理 executor.submit(() -> currentState.handleAsync());
七、奥斯卡颁奖典礼
状态模式让代码成为程序界的影帝:
- ✅ 要:用于行为随状态变化的场景
- ✅ 要:把状态转换逻辑封装在状态类中
- ❌ 不要:让Context变成状态上帝
- ❌ 不要:创建过多细碎状态类
当你在电商网站看到"订单已完成"时,请想起状态模式——那个在后台默默演绎了十几种状态变迁的无名影帝!