状态模式:让对象的行为随状态而变
状态模式,说白了就是让一个对象在内部状态改变时,它的行为也跟着变。听起来像不像一个多面手?我刚开始接触这个模式时,觉得它跟策略模式长得太像了,后来在项目中踩过坑才真正理解——它们俩的出发点完全不同。
什么是状态模式?
状态模式允许一个对象在其内部状态改变时改变它的行为。这个对象看起来就像修改了它的类一样。嗯,这里要注意,它把状态相关的行为封装到独立的状态类中,然后将对象的“当前状态”委托给对应的状态实例去处理。
举个例子,你想想看:一个订单系统,订单有“待支付”、“已支付”、“已发货”、“已完成”等状态。每个状态下,用户能做的操作完全不同。待支付时可以取消,已支付后就不能取消了。用一堆 if-else 去判断?代码会变得又臭又长。
模式结构
状态模式的结构其实很清晰,我画了一张图帮你理解:
核心角色就这几个:
- Context(上下文):持有当前状态的引用,对外暴露统一接口。它自己不处理状态逻辑,而是委托给状态对象。
- State(状态接口):定义所有具体状态必须实现的方法。
- ConcreteState(具体状态):实现状态接口,封装该状态下的具体行为。同时负责决定何时切换到下一个状态。
代码实现
我们用一个简单的订单状态机来演示。我个人习惯先写状态接口,再写具体状态,最后写上下文。
// 状态接口
public interface OrderState {
void pay(OrderContext context);
void ship(OrderContext context);
void deliver(OrderContext context);
void cancel(OrderContext context);
}
// 待支付状态
public class PendingState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("支付成功,订单进入已支付状态");
context.setState(new PaidState());
}
@Override
public void ship(OrderContext context) {
System.out.println("订单未支付,无法发货");
}
@Override
public void deliver(OrderContext context) {
System.out.println("订单未支付,无法配送");
}
@Override
public void cancel(OrderContext context) {
System.out.println("订单已取消");
context.setState(new CancelledState());
}
}
// 已支付状态
public class PaidState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("订单已支付,请勿重复支付");
}
@Override
public void ship(OrderContext context) {
System.out.println("发货成功,订单进入已发货状态");
context.setState(new ShippedState());
}
@Override
public void deliver(OrderContext context) {
System.out.println("订单未发货,无法配送");
}
@Override
public void cancel(OrderContext context) {
System.out.println("已支付订单取消,退款处理中");
context.setState(new CancelledState());
}
}
// 上下文
public class OrderContext {
private OrderState currentState;
public OrderContext() {
this.currentState = new PendingState(); // 初始状态
}
public void setState(OrderState state) {
this.currentState = state;
}
public void pay() { currentState.pay(this); }
public void ship() { currentState.ship(this); }
public void deliver() { currentState.deliver(this); }
public void cancel() { currentState.cancel(this); }
}
💡 我的经验:状态转换的逻辑放在具体状态类里,还是放在上下文里?我建议放在具体状态类中。因为每个状态最清楚自己可以转换到哪些状态。如果放在上下文里,上下文就会变得臃肿,而且每新增一个状态都要改上下文,违反了开闭原则。
优缺点分析
| 优点 | 缺点 |
|---|---|
| 将状态相关的行为集中管理,符合单一职责 | 状态类数量会增多,系统复杂度上升 |
| 消除庞大的条件分支语句 | 状态转换逻辑分散在各个状态类中,不够集中 |
| 新增状态很方便,符合开闭原则 | 如果状态很少或很少变化,使用状态模式反而过度设计 |
| 状态转换逻辑清晰,易于维护 | 状态类之间可能产生依赖,形成循环依赖 |
⚠️ 避坑指南:我曾经在一个项目中,把状态转换逻辑同时放在上下文和状态类里,结果出现了状态不一致的bug。后来统一放在状态类中,问题才解决。记住:状态转换的决策权要交给状态自己,上下文只负责执行。
应用场景
状态模式适合哪些场景?我总结了几类:
- 工作流引擎:审批流程、订单流转、工单处理等,每个步骤都有明确的状态和转换规则。
- 游戏开发:角色的状态(行走、奔跑、跳跃、攻击),每个状态下按键响应不同。
- 网络连接:TCP连接的状态(CLOSED、LISTEN、ESTABLISHED等),不同状态下对数据包的处理不同。
- UI组件:按钮的可用/不可用状态、加载状态、错误状态等。
你想想看,如果没有状态模式,这些场景会写成什么样?满屏的 if (state == A) { ... } else if (state == B) { ... }。改一个状态逻辑,要翻遍整个文件。用状态模式,每个状态独立成类,改起来就轻松多了。
核心要点:状态模式的核心思想是“将状态相关的行为封装到独立的状态类中”。它让对象的行为随着内部状态的改变而改变,看起来就像对象换了一个类。记住:状态模式关注的是“状态”本身,而不是“算法”的替换——这是它和策略模式最大的区别。
我记得有一次重构一个老项目,里面有个订单处理模块,状态判断嵌套了五六层 if-else。我花了三天时间改造成状态模式,代码量虽然增加了,但可读性和可维护性提升了好几个档次。后来新增了一个“退款中”状态,只加了一个类,改了两处转换逻辑,半小时搞定。要是放在以前,估计又得翻半天代码。
嗯,状态模式就是这样——前期多花点心思设计,后期省心省力。