状态模式(State):让对象的行为随状态而变
状态模式,说白了就是「对象内部状态变了,它的行为也跟着变」。听起来像废话?嗯,你想想看,我们平时写代码,遇到状态判断,第一反应就是 if-else 或者 switch-case。但一旦状态多了,条件分支就会膨胀得像个气球,改一个地方可能炸一片。
我个人习惯用状态模式来替代这种「面条式」的条件判断。它把每个状态封装成一个独立的类,状态之间的切换由状态类自己负责。这样主对象就干净了,只保留一个指向当前状态的引用。
状态模式的结构
状态模式有三个核心角色:
- Context(上下文):持有当前状态的引用,对外暴露统一的接口。它自己不处理行为,而是委托给状态对象。
- State(抽象状态):定义所有状态必须实现的接口。通常是一个抽象类或接口。
- ConcreteState(具体状态):实现状态相关的行为,并且负责在合适的时机切换到下一个状态。
我画了一张结构图,帮你理清它们的关系:
你看,Context 只认识 State 接口,具体状态之间怎么跳转,它根本不管。这就是「把变化封装起来」的典型做法。
状态模式 vs 策略模式
这两个模式长得太像了,很多初学者会搞混。我当年也踩过这个坑——有一次把订单状态流转用策略模式实现,结果状态切换逻辑散得到处都是,维护起来想骂人。
它们的核心区别在于:
| 维度 | 状态模式 | 策略模式 |
|---|---|---|
| 目的 | 管理对象的状态切换 | 封装可互换的算法 |
| 状态/策略间关系 | 状态之间会相互切换 | 策略之间彼此独立 |
| 谁控制切换 | 状态类自己决定下一个状态 | 客户端选择策略 |
| 典型场景 | 订单状态、工作流 | 排序算法、支付方式 |
说白了,策略模式是「换算法」,状态模式是「换身份」。你用策略模式时,客户端主动选一个策略塞进去。用状态模式时,对象自己根据当前状态决定下一步去哪。
一句话记住:策略模式是「我选谁」,状态模式是「我是谁」。
状态模式在订单状态流转中的应用
订单系统是状态模式最经典的战场。一个订单从创建到完成,要经历:待支付 → 已支付 → 已发货 → 已收货 → 已完成。每个状态下能做的操作完全不同。
我曾经接手过一个老项目,订单状态判断写在一个大 Service 类里,光 switch-case 就有七八层。加一个新状态要改三个地方,还经常漏掉。后来我用状态模式重构了它。
先看抽象状态接口:
public interface OrderState {
void pay(OrderContext context);
void ship(OrderContext context);
void receive(OrderContext context);
void cancel(OrderContext context);
}
然后是实现「待支付」状态:
public class UnpaidState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("支付成功,进入已支付状态");
context.setState(new PaidState());
}
@Override
public void ship(OrderContext context) {
throw new IllegalStateException("未支付不能发货");
}
@Override
public void receive(OrderContext context) {
throw new IllegalStateException("未支付不能收货");
}
@Override
public void cancel(OrderContext context) {
System.out.println("取消订单,进入已取消状态");
context.setState(new CancelledState());
}
}
再看「已支付」状态:
public class PaidState implements OrderState {
@Override
public void pay(OrderContext context) {
throw new IllegalStateException("已支付,不能重复支付");
}
@Override
public void ship(OrderContext context) {
System.out.println("发货成功,进入已发货状态");
context.setState(new ShippedState());
}
@Override
public void receive(OrderContext context) {
throw new IllegalStateException("未发货不能收货");
}
@Override
public void cancel(OrderContext context) {
System.out.println("已支付订单取消,进入退款流程");
context.setState(new RefundingState());
}
}
最后是 Context 类:
public class OrderContext {
private OrderState state;
public OrderContext() {
this.state = new UnpaidState(); // 初始状态
}
public void setState(OrderState state) {
this.state = state;
}
public void pay() {
state.pay(this);
}
public void ship() {
state.ship(this);
}
public void receive() {
state.receive(this);
}
public void cancel() {
state.cancel(this);
}
}
我的经验:状态模式特别适合状态数量固定、状态转换规则明确的场景。如果状态经常增删,或者转换逻辑频繁变化,那就要考虑是不是该用状态机框架了。
避坑指南
我曾经在一个项目中,把状态对象设计成单例,想着省内存。结果状态之间共享了不该共享的数据,导致并发下状态错乱。后来我改成每次切换都 new 一个新实例,问题就解决了。
还有一点要注意:状态类里不要持有 Context 的强引用,否则容易造成循环依赖。我习惯把 Context 作为参数传进去,而不是存在状态对象的字段里。
警告:状态模式不是万能的。如果状态只有两三个,或者状态转换逻辑极其简单,用 if-else 反而更清晰。别为了用模式而用模式。
总结
状态模式的核心思想就一句话:把状态相关的行为封装到独立的状态类中,让对象的状态切换变得透明且可维护。它和策略模式的区别在于「切换由谁控制」——状态模式是状态自己决定下一步,策略模式是客户端决定用哪个算法。
订单状态流转是状态模式的经典应用。每个状态只关心自己能做什么、做完后去哪。这样加一个新状态,只需要新增一个类,改一下相关状态的转换逻辑,完全符合开闭原则。
嗯,如果你现在正被一堆 if-else 折磨,不妨试试状态模式。但记住,别过度设计。