状态模式:封装状态变化的行为设计艺术
一、状态模式的定义与核心思想
状态模式(State Pattern) 是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其核心思想是 “将对象的状态封装为独立的状态类,将状态转换逻辑与状态对应的行为分离”—— 通过将不同状态下的行为封装到具体状态类中,让上下文对象(持有状态的对象)在状态变化时,自动切换对应的行为,无需在上下文类中编写大量条件判断语句。
简单来说,状态模式解决了 “对象因状态变化导致行为切换,进而产生大量条件判断” 的问题。例如,电梯的运行状态(停止、上升、下降)、订单的生命周期(待支付、已支付、已发货、已完成)、播放器的状态(播放、暂停、停止)等场景,都适合用状态模式封装状态变化与对应行为。
二、状态模式的角色构成
状态模式通常包含以下 3 个核心角色,各角色职责明确、协作完成状态切换与行为执行:
| 角色名称 | 核心职责 | 示例(电梯运行场景) |
|---|---|---|
| 上下文(Context) | 持有当前状态对象,提供状态切换的接口,对外暴露统一的行为方法(委托给当前状态对象执行) | Elevator 类(电梯) |
| 抽象状态(State) | 定义所有具体状态的公共接口,声明状态对应的行为方法(如电梯的开门、关门、运行方法) | ElevatorState 接口 |
| 具体状态(ConcreteState) | 实现抽象状态接口,封装该状态下的具体行为,同时可包含状态转换的逻辑(如从 “停止” 状态切换到 “上升” 状态) | StoppedState(停止状态)、RunningUpState(上升状态)、RunningDownState(下降状态) |
三、状态模式的工作原理与实现
1. 工作流程
-
上下文对象初始化时,设置初始状态(如电梯初始状态为 “停止”);
-
客户端调用上下文的行为方法(如电梯的 “上升” 方法);
-
上下文对象将行为委托给当前持有的状态对象执行;
-
具体状态对象执行对应行为,若行为触发状态变化(如电梯从 “停止” 变为 “上升”),则由状态对象通知上下文切换到新状态;
-
后续客户端调用上下文行为时,将委托给新的状态对象执行。
2. 基础实现(电梯运行状态管理)
以电梯的核心状态(停止、上升、下降)为例,实现状态模式,封装不同状态下的行为与状态转换:
(1)抽象状态(State)
// 抽象状态:电梯状态接口,定义电梯的核心行为public interface ElevatorState {void openDoor(); // 开门void closeDoor(); // 关门void runUp(); // 上升void runDown(); // 下降void stop(); // 停止}
(2)上下文(Context)
// 上下文:电梯类,持有当前状态,对外暴露统一行为接口public class Elevator {// 定义所有可能的状态(也可通过工厂模式创建,此处简化)public final ElevatorState STOPPED_STATE = new StoppedState(this);public final ElevatorState RUNNING_UP_STATE = new RunningUpState(this);public final ElevatorState RUNNING_DOWN_STATE = new RunningDownState(this);private ElevatorState currentState; // 当前状态// 初始化:电梯默认处于停止状态public Elevator() {this.currentState = STOPPED_STATE;System.out.println("电梯初始状态:停止");}// 状态切换方法:由具体状态对象调用,切换当前状态public void setState(ElevatorState state) {this.currentState = state;}// 对外暴露的行为方法:委托给当前状态执行public void openDoor() {currentState.openDoor();}public void closeDoor() {currentState.closeDoor();}public void runUp() {currentState.runUp();}public void runDown() {currentState.runDown();}public void stop() {currentState.stop();}}
(3)具体状态(ConcreteState)
① 停止状态(StoppedState)
// 具体状态:停止状态,实现停止状态下的行为与状态转换public class StoppedState implements ElevatorState {private Elevator elevator; // 持有上下文引用,用于切换状态public StoppedState(Elevator elevator) {this.elevator = elevator;}@Overridepublic void openDoor() {// 停止状态下可开门System.out.println("电梯处于停止状态,开门成功~");}@Overridepublic void closeDoor() {// 停止状态下可关门(若已开门)System.out.println("电梯处于停止状态,关门成功~");}@Overridepublic void runUp() {// 停止状态下可切换为上升状态System.out.println("电梯从停止状态开始上升~");elevator.setState(elevator.RUNNING_UP_STATE); // 切换到上升状态}@Overridepublic void runDown() {// 停止状态下可切换为下降状态System.out.println("电梯从停止状态开始下降~");elevator.setState(elevator.RUNNING_DOWN_STATE); // 切换到下降状态}@Overridepublic void stop() {// 已处于停止状态,无需操作System.out.println("电梯已处于停止状态,无需重复停止~");}}
② 上升状态(RunningUpState)
// 具体状态:上升状态,实现上升状态下的行为与状态转换public class RunningUpState implements ElevatorState {private Elevator elevator;public RunningUpState(Elevator elevator) {this.elevator = elevator;}@Overridepublic void openDoor() {// 上升状态下不可开门(安全限制)System.out.println("电梯正在上升,无法开门!");}@Overridepublic void closeDoor() {// 上升状态下门已关闭,无需操作System.out.println("电梯正在上升,门已关闭~");}@Overridepublic void runUp() {// 已处于上升状态,无需操作System.out.println("电梯正在上升,保持运行~");}@Overridepublic void runDown() {// 上升状态下不可直接切换为下降状态(需先停止)System.out.println("电梯正在上升,无法直接下降!请先停止~");}@Overridepublic void stop() {// 上升状态下可切换为停止状态System.out.println("电梯停止上升,切换为停止状态~");elevator.setState(elevator.STOPPED_STATE); // 切换到停止状态}}
③ 下降状态(RunningDownState)
// 具体状态:下降状态,实现下降状态下的行为与状态转换public class RunningDownState implements ElevatorState {private Elevator elevator;public RunningDownState(Elevator elevator) {this.elevator = elevator;}@Overridepublic void openDoor() {// 下降状态下不可开门(安全限制)System.out.println("电梯正在下降,无法开门!");}@Overridepublic void closeDoor() {// 下降状态下门已关闭,无需操作System.out.println("电梯正在下降,门已关闭~");}@Overridepublic void runUp() {// 下降状态下不可直接切换为上升状态(需先停止)System.out.println("电梯正在下降,无法直接上升!请先停止~");}@Overridepublic void runDown() {// 已处于下降状态,无需操作System.out.println("电梯正在下降,保持运行~");}@Overridepublic void stop() {// 下降状态下可切换为停止状态System.out.println("电梯停止下降,切换为停止状态~");elevator.setState(elevator.STOPPED_STATE); // 切换到停止状态}}
(4)客户端测试
public class Client {public static void main(String[] args) {// 创建上下文对象(电梯)Elevator elevator = new Elevator();System.out.println("n1. 测试停止状态下的操作:");elevator.openDoor(); // 停止状态可开门elevator.closeDoor(); // 停止状态可关门elevator.runUp(); // 停止状态切换为上升System.out.println("n2. 测试上升状态下的操作:");elevator.openDoor(); // 上升状态不可开门elevator.runUp(); // 上升状态保持运行elevator.stop(); // 上升状态切换为停止System.out.println("n3. 测试停止状态切换为下降:");elevator.runDown(); // 停止状态切换为下降System.out.println("n4. 测试下降状态下的操作:");elevator.runUp(); // 下降状态不可直接上升elevator.stop(); // 下降状态切换为停止}}
输出结果
电梯初始状态:停止1. 测试停止状态下的操作:电梯处于停止状态,开门成功~电梯处于停止状态,关门成功~电梯从停止状态开始上升~2. 测试上升状态下的操作:电梯正在上升,无法开门!电梯正在上升,保持运行~电梯停止上升,切换为停止状态~3. 测试停止状态切换为下降:电梯从停止状态开始下降~4. 测试下降状态下的操作:电梯正在下降,无法直接上升!请先停止~电梯停止下降,切换为停止状态~
3. 扩展:状态模式的灵活扩展(新增 “故障状态”)
状态模式的核心优势是易于扩展新状态,无需修改原有状态类和上下文类,只需新增具体状态类并实现抽象状态接口:
(1)新增具体状态:故障状态(FaultState)
// 新增具体状态:故障状态public class FaultState implements ElevatorState {private Elevator elevator;public FaultState(Elevator elevator) {this.elevator = elevator;}@Overridepublic void openDoor() {// 故障状态下仅允许手动开门(紧急情况)System.out.println("电梯处于故障状态,紧急开门成功(仅手动操作)~");}@Overridepublic void closeDoor() {System.out.println("电梯处于故障状态,关门成功~");}@Overridepublic void runUp() {System.out.println("电梯处于故障状态,无法上升!");}@Overridepublic void runDown() {System.out.println("电梯处于故障状态,无法下降!");}@Overridepublic void stop() {// 故障状态下停止操作无效(已停止运行)System.out.println("电梯处于故障状态,已停止运行~");}}
(2)上下文扩展(添加故障状态)
public class Elevator {// 新增故障状态public final ElevatorState FAULT_STATE = new FaultState(this);// 其他代码不变...// 新增故障触发方法(对外暴露)public void triggerFault() {System.out.println("n触发电梯故障!");this.setState(FAULT_STATE);}}
(3)扩展客户端测试
public class Client {public static void main(String[] args) {Elevator elevator = new Elevator();System.out.println("n5. 测试故障状态:");elevator.triggerFault(); // 触发故障elevator.openDoor(); // 故障状态紧急开门elevator.runUp(); // 故障状态无法上升elevator.stop(); // 故障状态已停止}}
扩展输出结果
5. 测试故障状态:触发电梯故障!电梯处于故障状态,紧急开门成功(仅手动操作)~电梯处于故障状态,无法上升!电梯处于故障状态,已停止运行~
四、状态模式的应用场景
状态模式适用于以下场景:
-
对象行为随状态变化而变化:例如,订单状态(待支付→已支付→已发货→已完成),不同状态下的订单支持的操作(取消订单、退款、确认收货)不同。
-
避免大量条件判断语句:若上下文类中存在大量
if-else或switch-case语句判断状态并执行不同行为,适合用状态模式重构(将条件判断转换为状态类的多态调用)。 -
状态转换逻辑复杂:状态之间的转换规则较多,且转换逻辑与状态行为紧密相关(如电梯的状态转换受安全规则限制)。
-
需要动态切换状态:对象在运行时需根据外部事件动态切换状态,且切换后行为需自动调整(如播放器的播放 / 暂停 / 停止状态切换)。
五、状态模式的优缺点
优点
-
封装状态与行为:将不同状态下的行为封装到独立的状态类中,符合 “单一职责原则”,代码结构清晰。
-
消除条件判断:避免上下文类中大量的条件判断语句,通过状态类的多态调用实现行为切换,提高代码可维护性。
-
易于扩展新状态:新增状态只需实现抽象状态接口,无需修改原有代码,符合 “开闭原则”(如电梯新增故障状态无需改动原有状态类)。
-
状态转换逻辑清晰:状态转换规则集中在具体状态类中,便于理解和维护。
缺点
-
类数量增多:每个状态对应一个具体状态类,若状态数量较多(如订单有 10 种状态),会导致系统中类的数量激增,增加开发和维护成本。
-
状态类与上下文耦合:具体状态类持有上下文引用(用于切换状态),导致状态类与上下文存在一定耦合(可通过引入状态管理器降低耦合)。
-
简单状态场景冗余:若对象只有少数几种状态,且状态转换逻辑简单,使用状态模式会增加不必要的复杂性(直接用条件判断更简洁)。
六、状态模式与命令模式的核心区别
状态模式与命令模式均为行为型设计模式,且都通过封装实现解耦,但核心目标和应用场景差异显著:
| 维度 | 状态模式 | 命令模式 |
|---|---|---|
| 核心目标 | 封装状态变化与状态对应的行为 | 封装请求,解耦发送者与接收者 |
| 角色关系 | 上下文持有状态,状态依赖上下文(双向关联) | 调用者持有命令,命令持有接收者(单向关联) |
| 行为触发 | 行为由当前状态决定(状态驱动) | 行为由命令对象决定(请求驱动) |
| 状态 / 命令复用 | 状态可在多个上下文间复用 | 命令可在多个调用者间复用 |
| 典型场景 | 电梯状态、订单状态、播放器状态 | 遥控器操作、撤销 / 重做、命令队列 |
简单来说:状态模式是 “状态决定行为”,命令模式是 “请求触发行为”—— 状态模式聚焦对象自身的状态变化,命令模式聚焦请求的发送与执行分离。