状态模式(State Pattern)
超级通俗一句话 :把对象的不同状态 封装成独立的状态类 ,让对象在状态变化时,自动切换行为逻辑,就像电梯运行 ------电梯有「停止、上升、下降」3种状态,按开门键时,停止状态 会开门,上升/下降状态 则忽略操作,状态决定行为 ,而非一堆 if/else 判断。
核心是用"状态类"替代"状态判断分支" ,解决大量 if-else/switch 导致的代码臃肿、难以维护问题,这也是它和其他行为型模式最核心的区别。
一、核心特点(一眼记住)
- 状态与行为绑定:不同状态对应不同行为,状态变,行为自动变,无需手动判断;
- 状态封装独立 :每个状态都是一个独立的类,符合单一职责,新增/修改状态不影响其他状态;
- 对象状态自动化:上下文对象(如电梯)持有当前状态,状态间的切换可由状态类自身控制(如电梯上升到目标楼层后,自动切换为停止状态);
- 消除分支判断 :彻底替代
if/else/switch,让代码更简洁、易扩展(符合开闭原则)。
项目中常见场景:
- 有限状态机(FSM):如电梯、订单、游戏角色状态;
- 业务流程状态流转:如订单「待支付→已支付→待发货→已发货→已完成」;
- 设备状态控制:如空调「制冷→制热→送风→关机」;
- 游戏开发:如角色「正常→受伤→死亡→复活」。
二、标准结构(3个核心角色,极简且固定)
状态模式的结构是所有行为型模式中最简洁的之一,3个角色各司其职,无冗余,这也是它易落地、易维护的原因:
- State(抽象状态) :定义所有状态的统一行为接口,包含该状态下要执行的方法,同时可定义状态切换的通用方法;
- ConcreteState(具体状态) :实现抽象状态接口,封装当前状态下的具体行为 ,并负责状态的切换逻辑(决定何时切换到哪个状态);
- Context(上下文) :持有当前状态对象,是外部交互的入口,对外提供统一的业务方法,实际行为由当前状态对象执行,自身不包含状态判断逻辑。
角色关系梳理 :
上下文 → 持有当前具体状态 → 外部调用上下文的方法 → 上下文委托当前状态执行具体行为 → 具体状态执行行为并决定是否切换到新状态 → 上下文更新当前状态。
三、Java代码实战(最简单版本,电梯案例)
用电梯运行 的经典场景实现,完美贴合3个角色,彻底替代 if/else,一看就懂,代码无冗余!
业务场景:电梯有3种核心状态,行为严格绑定状态
- 停止状态:可开门、可按楼层(切换为上升/下降);
- 上升状态:不可开门、到达目标楼层后自动切换为停止状态;
- 下降状态:不可开门、到达目标楼层后自动切换为停止状态。
1. State(抽象状态:电梯状态)
定义电梯所有状态的统一行为接口:开门、运行、停止。
java
// 抽象状态:电梯状态
public interface ElevatorState {
// 开门操作
void openDoor();
// 运行操作(上升/下降)
void run();
// 停止操作
void stop();
}
2. ConcreteState(具体状态:3种电梯状态,封装行为+状态切换)
每个状态类只实现自己的行为,不符合当前状态的行为直接做友好提示,并负责状态切换逻辑。
① 停止状态(电梯静止时)
java
// 具体状态1:停止状态
public class StopState implements ElevatorState {
// 持有上下文:用于切换状态
private ElevatorContext context;
public StopState(ElevatorContext context) {
this.context = context;
}
@Override
public void openDoor() {
// 停止状态:可开门
System.out.println("电梯【停止状态】:开门成功,请上下楼!");
}
@Override
public void run() {
// 停止状态:运行(切换为上升/下降,这里简化为直接切换上升)
System.out.println("电梯【停止状态】:开始运行,切换为上升状态!");
context.setCurrentState(context.getUpState());
}
@Override
public void stop() {
// 停止状态:再次停止,无操作
System.out.println("电梯【停止状态】:已静止,无需重复停止!");
}
}
② 上升状态(电梯上升时)
java
// 具体状态2:上升状态
public class UpState implements ElevatorState {
private ElevatorContext context;
public UpState(ElevatorContext context) {
this.context = context;
}
@Override
public void openDoor() {
// 上升状态:不可开门
System.out.println("电梯【上升状态】:运行中禁止开门,危险!");
}
@Override
public void run() {
// 上升状态:持续运行,无操作
System.out.println("电梯【上升状态】:正在上升,目标楼层5楼!");
}
@Override
public void stop() {
// 上升状态:到达楼层,停止并切换为停止状态
System.out.println("电梯【上升状态】:到达目标楼层,停止运行,切换为停止状态!");
context.setCurrentState(context.getStopState());
}
}
③ 下降状态(电梯下降时)
java
// 具体状态3:下降状态
public class DownState implements ElevatorState {
private ElevatorContext context;
public DownState(ElevatorContext context) {
this.context = context;
}
@Override
public void openDoor() {
// 下降状态:不可开门
System.out.println("电梯【下降状态】:运行中禁止开门,危险!");
}
@Override
public void run() {
// 下降状态:持续运行,无操作
System.out.println("电梯【下降状态】:正在下降,目标楼层1楼!");
}
@Override
public void stop() {
// 下降状态:到达楼层,停止并切换为停止状态
System.out.println("电梯【下降状态】:到达目标楼层,停止运行,切换为停止状态!");
context.setCurrentState(context.getStopState());
}
}
3. Context(上下文:电梯)
外部交互的唯一入口,持有所有状态对象和当前状态,对外提供统一方法,自身不做任何业务逻辑,全部委托给当前状态。
java
// 上下文:电梯(外部唯一交互入口)
public class ElevatorContext {
// 定义所有状态对象(单例,避免重复创建)
private final ElevatorState stopState;
private final ElevatorState upState;
private final ElevatorState downState;
// 持有当前状态(核心)
private ElevatorState currentState;
// 构造器:初始化所有状态,默认初始状态为停止
public ElevatorContext() {
this.stopState = new StopState(this);
this.upState = new UpState(this);
this.downState = new DownState(this);
this.currentState = stopState;
}
// 对外提供的统一方法:开门
public void openDoor() {
currentState.openDoor();
}
// 对外提供的统一方法:运行
public void run() {
currentState.run();
}
// 对外提供的统一方法:停止
public void stop() {
currentState.stop();
}
// 状态切换的setter/getter
public void setCurrentState(ElevatorState currentState) {
this.currentState = currentState;
}
public ElevatorState getStopState() { return stopState; }
public ElevatorState getUpState() { return upState; }
public ElevatorState getDownState() { return downState; }
}
4. 测试使用(外部只与上下文交互,无需关心状态)
java
// 测试类:外部调用者
public class StateTest {
public static void main(String[] args) {
// 1. 创建上下文:电梯
ElevatorContext elevator = new ElevatorContext();
System.out.println("===== 初始状态:停止 =====");
// 2. 调用开门(停止状态可开门)
elevator.openDoor();
// 3. 调用运行(停止状态→上升状态)
elevator.run();
System.out.println("===== 切换后状态:上升 =====");
// 4. 上升状态尝试开门(禁止)
elevator.openDoor();
// 5. 上升状态持续运行
elevator.run();
// 6. 上升状态停止(→停止状态)
elevator.stop();
System.out.println("===== 切换后状态:停止 =====");
// 7. 再次开门(可开门)
elevator.openDoor();
}
}
运行结果(状态自动切换,行为严格绑定状态)
===== 初始状态:停止 =====
电梯【停止状态】:开门成功,请上下楼!
电梯【停止状态】:开始运行,切换为上升状态!
===== 切换后状态:上升 =====
电梯【上升状态】:运行中禁止开门,危险!
电梯【上升状态】:正在上升,目标楼层5楼!
电梯【上升状态】:到达目标楼层,停止运行,切换为停止状态!
===== 切换后状态:停止 =====
电梯【停止状态】:开门成功,请上下楼!
四、扩展:新增状态(零改动,符合开闭原则)
如果想给电梯加故障状态 (所有操作都提示故障),只需新增一个具体状态类 ,修改上下文的初始化代码,无需改动任何原有状态类和业务逻辑,这就是状态模式的核心优势:
1. 新增具体状态:故障状态
java
// 新增具体状态:故障状态
public class FaultState implements ElevatorState {
public FaultState(ElevatorContext context) {
// 无需持有上下文,故障状态不切换其他状态
}
@Override
public void openDoor() {
System.out.println("电梯【故障状态】:设备故障,无法开门!");
}
@Override
public void run() {
System.out.println("电梯【故障状态】:设备故障,无法运行!");
}
@Override
public void stop() {
System.out.println("电梯【故障状态】:已故障停机,请联系维修!");
}
}
2. 上下文新增故障状态(仅修改此处)
java
// 电梯上下文新增故障状态
public class ElevatorContext {
private final ElevatorState faultState; // 新增故障状态
// 其他原有状态不变...
public ElevatorContext() {
// 原有初始化不变...
this.faultState = new FaultState(this); // 初始化故障状态
this.currentState = stopState;
}
// 新增:切换故障状态的方法
public void fault() {
System.out.println("电梯:发生故障,切换为故障状态!");
this.currentState = faultState;
}
// 新增getter...
public ElevatorState getFaultState() { return faultState; }
}
3. 外部调用新增状态(无侵入式)
java
// 测试新增故障状态
elevator.fault(); // 切换为故障状态
elevator.openDoor(); // 故障状态无法开门
elevator.run(); // 故障状态无法运行
运行结果
电梯:发生故障,切换为故障状态!
电梯【故障状态】:设备故障,无法开门!
电梯【故障状态】:设备故障,无法运行!
五、和前4种模式的核心区别(一张表秒懂)
你已经学了代理、装饰器、责任链、命令、状态 5种行为型模式,均易混淆,用核心目的+核心特征 做终极对比,彻底区分,结合你的麻将游戏项目,一眼就能判断该用哪种:
| 模式 | 核心目的 | 核心特征 | 典型场景 | 麻将项目适用场景 |
|---|---|---|---|---|
| 代理模式 | 控制访问(替身) | 代理持有一个真实对象 | 权限、远程调用、懒加载 | 玩家操作权限校验、远程房间调用 |
| 装饰器模式 | 增强功能(套娃) | 装饰器持有一个组件,层层包装 | IO流、日志/缓存增强 | 玩家出牌日志、牌型计算增强 |
| 责任链模式 | 请求流转(谁能处理谁处理) | 处理器持有下一个处理器,串链 | 审批流、过滤器 | 出牌合法性校验链(牌型→规则→次数) |
| 命令模式 | 解耦请求与执行(封装操作) | 命令持有一个接收者,调用者持命令 | 按钮操作、撤销/恢复 | 打牌/碰牌/杠牌操作封装、撤销 |
| 状态模式 | 状态决定行为(消除分支) | 上下文持有当前状态,状态封装行为 | 有限状态机、状态流转 | 房间/游戏状态控制(待开局→游戏中→游戏结束) |
一句话极简区分:
- 代理:替你做(控制访问);
- 装饰器:给你加功能(套娃增强);
- 责任链:顺着链条传(分发处理);
- 命令:封装起来让别人做(解耦请求与执行);
- 状态:什么状态做什么事(状态绑定行为,消除if/else)。
六、状态模式的核心优势&劣势&适用场景
核心优势
- 消除分支判断 :彻底替代
if/else/switch,解决代码臃肿、难以维护的问题(这是最核心的价值); - 状态封装独立:每个状态都是独立类,符合单一职责,新增/修改状态无侵入,易扩展;
- 状态逻辑清晰:状态的行为和切换逻辑都在对应状态类中,便于调试和维护;
- 行为自动化:状态切换由状态类自身控制,上下文无需关心,降低耦合。
轻微劣势
- 类数量增加:每个状态对应一个类,状态较多时会产生一定的类数量(但相比混乱的if/else,这是可接受的代价);
- 状态切换复杂时需注意 :若状态间切换逻辑过于复杂,可能会导致状态类之间产生耦合(可通过状态管理器优化)。
最佳适用场景
当对象的行为由其状态决定,且存在大量状态判断分支时,优先使用状态模式,具体为:
- 对象有有限的、明确的状态(如订单5种状态、电梯3种状态);
- 不同状态下行为差异明显 ,且状态间有明确的切换规则;
- 代码中存在大量
if/else/switch判断状态,且维护成本高。
七、超级总结(记住3点,落地无忧)
- 核心思想 :状态决定行为,将每个状态封装为独立类,让对象的行为随状态自动变化;
- 关键特征 :3个核心角色,上下文持有当前状态 ,状态类封装行为和切换逻辑,外部只与上下文交互;
- 核心价值 :消除大量分支判断,让状态相关的代码更简洁、易扩展、易维护,是实现**有限状态机(FSM)**的最佳设计模式。
代理:替你做,控制访问;
装饰器:给你加功能,套娃增强;
责任链:顺着链条传,谁能处理谁处理;
命令:封装操作,解耦请求与执行,可撤销;
策略:外部选算法,算法可互换;
状态:内部自动切换状态,状态决定行为,消除 if/else。