1. 状态模式到底在解决什么问题
先说个我早期写代码的真实经历。那时候接了一个订单系统的需求,订单有“待支付”“已支付”“已发货”“已完成”“已取消”这么几种状态,每个状态下用户能做的操作完全不一样:待支付能取消,已支付不能取消只能等发货,已发货能确认收货,已完成就只能申请售后了。我图省事,直接在一个Service类里写了几个方法,每个方法开头就是一长串if判断当前状态,像这样:
java复制public String cancel(Long orderId) {
Order order = orderDao.findById(orderId);
if ("待支付".equals(order.getStatus())) {
// 执行取消逻辑
} else if ("已支付".equals(order.getStatus())) {
// 提示取消失败
} else {
// 其他各种状态的处理
}
return ...;
}
刚开始状态少还好,后来需求方说还要加“退款中”“退款完成”“已关闭”……那个类直接膨胀到两千多行。每次改一个状态,都要把涉及这个状态的所有方法翻一遍,生怕漏掉某个分支。更要命的是,如果有人不小心在某个else里把状态判断写反了,线上就出bug了。那段日子让我深刻体会到一句话:用一堆if-else来管理状态流转,本质上是在手动维护一张状态机,而这张状态机散落在每个方法的犄角旮旯里。
状态模式解决的就是这个问题。它把“每个状态”封装成一个独立的类,状态自己的行为、状态允许跳转到哪些其他状态,都由这个类自己说了算。对象内部状态一旦改变,它的行为也跟着变,从外部看起来就像换了一个类一样。这就是状态模式的核心思想——允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
这篇文章适合正在学设计模式准备期末考试的同学、工作中被状态判断折磨的Java/C++工程师,以及想在Android源码或者其他框架里找到状态模式实际应用的人。我会把状态模式从原理讲到手写实现,再讲到我实际项目里的重构过程,最后聊聊它和策略模式那些经常被搞混的细节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 状态模式的三个角色和一个核心机制
2.1 状态模式的结构拆解
状态模式的结构不复杂,核心就三个角色。
Context(上下文):持有当前状态对象的引用,对外提供业务方法。Context不关心具体状态类内部怎么实现,它只负责把请求转发给当前状态对象,同时提供切换状态的入口。
State(抽象状态):定义了一个接口或者抽象类,声明所有状态都要实现的行为方法。比如订单的State接口可以定义doPay()、doCancel()、doShip()、doConfirm()这些方法。
ConcreteState(具体状态):实现State接口的具体类,每个类代表一种具体状态,并且实现这个状态下各个行为方法的具体逻辑。比如“待支付”状态下调用doPay()就是执行支付,“已支付”状态下调用doPay()可能就是抛异常或者提示“不能重复支付”。
这三者之间的协作机制很巧妙:Context里存着一个State引用,每次业务方法被调用,Context就把这个调用转发给当前State对象。当状态要发生变化时,Context调用一个setState方法把引用换成另一个State对象。整个过程对调用方完全透明,调用方只跟Context打交道,完全感受不到状态切换这件事。
2.2 一个最小可运行的Java示例
理论说多了容易晕,直接上代码。我用订单系统来演示,先定义State接口:
java复制public interface OrderState {
// 支付
void pay(OrderContext context);
// 取消
void cancel(OrderContext context);
// 发货
void ship(OrderContext context);
// 确认收货
void confirm(OrderContext context);
// 当前状态名称,方便打印
String getStateName();
}
然后写四个具体状态类,先看“待支付”状态:
java复制public class PendingPayState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("订单支付成功,状态从[待支付]变为[已支付]");
context.setState(new PaidState());
}
@Override
public void cancel(OrderContext context) {
System.out.println("订单已取消,状态从[待支付]变为[已取消]");
context.setState(new CanceledState());
}
@Override
public void ship(OrderContext context) {
System.out.println("订单还未支付,不能发货");
}
@Override
public void confirm(OrderContext context) {
System.out.println("订单还未支付,不能确认收货");
}
@Override
public String getStateName() {
return "待支付";
}
}
“已支付”状态:
java复制public class PaidState i
