1. 为什么你的代码越写越乱:状态判断与行为耦合的根源
1.1 一个天天见面的“状态驱动”场景
做业务开发的人对下面这类代码绝对不陌生:一个订单对象,根据状态字段决定当前能做什么事,if (state == 待支付) 执行支付逻辑,else if (state == 已发货) 执行确认收货逻辑,再往后还有退款、取消、售后,每一个分支里还嵌套着不同的业务操作。刚开始状态少,写得挺顺手,等状态从三个变成八个,每个状态下还有两三个动作时,函数体就开始膨胀。你不敢轻易动里面的逻辑,因为改一个分支可能牵扯到其他状态下的判断。这是典型的状态判断和业务行为被捆绑在一个方法里的问题。
状态模式(State Pattern)就是在这种场景下被提出来的一种行为型设计模式。它的核心思路是:把对象在不同状态下的行为,从一堆if-else/switch-case中拆出来,分散到各自独立的状态类里。对象内部状态变化时,行为也跟着变化,从外部看就像这个对象换了个类型一样。这个模式在状态机、协议解析、工作流引擎、游戏角色控制等场景里都有广泛应用。
1.2 状态模式到底在解决什么
很多人误以为状态模式只是为了少写几个if-else,这个理解太浅了。if-else本身不是问题,问题出在状态判断与业务逻辑的耦合上。
设想一个播放器对象:它在“停止”状态下点击“播放”按钮,应该开始播放;在“播放中”状态下重复点击“播放”按钮,可能没有任何响应或者弹提示;在“暂停”状态下点击“播放”,则恢复播放。同一个按钮事件,因为内部状态不同,执行结果完全不同。如果你不把“当前状态”当作一个一级公民来对待,而是散落在各个方法里到处判断,那么每次加一个状态,就要把所有相关方法都翻一遍,漏改一个就是线上事故。
状态模式解决的是“状态驱动的行为分发”问题。它把每一个状态封装成一个类,将“在这个状态下遇到这个事件应该做什么”和“做完之后转移到哪个状态”这两个信息内聚在一起。这带来的好处是:新增一个状态时,只需要新增一个状态类,并找到前一个状态里负责转移的地方做修改,而不是在一个大方法里到处加判断。
当然,状态模式不是银弹,它也有自己的适用边界。接下来我会从原理、代码、真实案例和坑位几个角度,把状态模式掰开揉碎讲清楚。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 状态机的内部结构与状态模式的三要素
2.1 从状态机看状态模式
理解状态模式前,先理解状态机。状态机是一个数学模型,由“当前状态”“事件”“转移动作”“目标状态”四部分组成。以电梯为例:当前状态可能是“静止”,事件是“按下上行按钮”,转移动作是“关闭电梯门并向上启动”,目标状态是“上行中”。
状态机描述的是系统在不同状态下对外部事件的响应方式。如果用代码实现一个状态机,最直接的做法就是维护一个状态变量,再写一个大的事件分发方法,内部用分支判断当前状态,这就是最原始的“面条式”状态机。而状态模式,本质上是一种把状态机的“转移逻辑”和“行为逻辑”分散到各个状态对象里的面向对象实现手法。它不创造状态机,它只是让状态机更好维护、更好扩展。
2.2 Context、State与ConcreteState的职责划分
状态模式有三个角色,先理清它们各自干什么,后面看代码才不会迷路。
- Context(环境角色):持有当前状态对象,并对外提供业务方法。客户端直接面向Context编程,不用关心内部状态怎么流转。Context内部维护一个
State引用,所有请求都委托给当前状态对象处理。 - State(抽象状态角色):定义一个接口或抽象类,声明该状态下所有可能处理的业务行为。注意,这里的行为是“事件”,不是“动作”,比如
handle(Context context)或者更业务化的payOrder()、confirmReceipt()。 - ConcreteState(具体状态角色):实现State接口,负责真正处理对应状态下的事件,并决定事件处理后是否把Context切换到另一个状态。
一句话概括:Context负责“找谁干活”,State负责“干什么活、干完去哪”。两者通过依赖注入关联,切换状态时Context替换掉持有引用即可。
下面我用一个通用的例子展示三个角色之间的关系。先定义一个状态接口:
java复制public interface State {
void doAction(Context context);
}
Context持有一个状态对象,并提供切换状态的方法:
java复制public class Context {
private State state;
public void setState(State state) {
this.state = state;
}
public void request() {
state.doAction(this);
}
}
具体状态类实现业务逻辑并执行转移:
java复制public class ConcreteStateA implements State {
@Override
public void doAction(Context context) {
System.out.println("当前处于状态A,执行A特有的逻辑");
context.setState(new ConcreteStateB());
}
}
这样一轮操作下来,外部调用context.request()时,第一次执行的是状态A的逻辑,然后Context内部悄悄变成了状态B,第二次调用执行的就是状态B的逻辑。外部无感知,内部状态已经完成了一次转移。这其实就是状态模式最朴素的骨架。
3. 订单状态流转:if-else 实现和状态模式实现的直接对比
3.1 第一版:分支判断写法
先写一个大家都在用的订单类,状态用整数常量表示:
java复制pu
