文章目录
-
- 前言
- [1. 状态模式是什么?](#1. 状态模式是什么?)
- [2. 状态模式解决什么问题?](#2. 状态模式解决什么问题?)
- [3. 核心结构](#3. 核心结构)
-
- [3.1 State(抽象状态)](#3.1 State(抽象状态))
- [3.2 ConcreteState(具体状态)](#3.2 ConcreteState(具体状态))
- [3.3 Context(上下文/环境)](#3.3 Context(上下文/环境))
- [4. 实现思路](#4. 实现思路)
- [5. 示例](#5. 示例)
-
- [5.1 State:抽象状态](#5.1 State:抽象状态)
- [5.2 ConcreteState:具体状态](#5.2 ConcreteState:具体状态)
-
- [5.2.1 待支付状态](#5.2.1 待支付状态)
- [5.2.2 已支付状态](#5.2.2 已支付状态)
- [5.2.3 已发货状态](#5.2.3 已发货状态)
- [5.2.4 已签收状态](#5.2.4 已签收状态)
- [5.3 Context:上下文](#5.3 Context:上下文)
- [5.4 Client:客户端使用](#5.4 Client:客户端使用)
- [6. 优缺点](#6. 优缺点)
-
- [6.1 优点](#6.1 优点)
- [6.2 缺点](#6.2 缺点)
- [7. 和其他模式怎么区分?](#7. 和其他模式怎么区分?)
-
- [7.1 状态模式 vs 策略模式](#7.1 状态模式 vs 策略模式)
- [7.2 状态模式 vs 责任链模式](#7.2 状态模式 vs 责任链模式)
- [7.3 状态模式 vs 有限状态机(FSM)](#7.3 状态模式 vs 有限状态机(FSM))
- [8. 适用场景](#8. 适用场景)
- [9. 实际应用](#9. 实际应用)
- [10. 总结](#10. 总结)
前言
在业务开发中,我们经常会遇到"一个对象在不同状态下,行为完全不同"的需求,比如:
- 订单系统:待支付 → 已支付 → 已发货 → 已签收 → 已完成
- 电梯控制:开门状态 → 关门状态 → 运行状态 → 停止状态
- 游戏角色:正常状态 → 受伤状态 → 中毒状态 → 死亡状态
- 交通信号灯:红灯 → 绿灯 → 黄灯
这些场景有一个共同点:对象的行为取决于它当前的状态,状态改变时行为也随之改变,而且不同状态之间存在明确的转换规则。
如果不用设计模式,我们通常会写出这样的代码:
java
public void doAction() {
if (state == "待支付") {
// 可以支付、可以取消
} else if (state == "已支付") {
// 可以发货、可以退款
} else if (state == "已发货") {
// 可以签收、可以退货
} else if (state == "已完成") {
// 可以评价
}
// 每新增一个状态,这里就多一个 else if...
}
状态越多,这坨 if/else 就越膨胀,改一个状态的逻辑可能牵连其他状态,维护起来让人头大。
状态模式(State Pattern)要解决的核心就是:
将对象在不同状态下的行为封装到独立的状态类中,对象内部状态改变时,自动切换行为,外部调用者无需关心当前是什么状态。
1. 状态模式是什么?
状态模式是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为,看起来就像改变了它的类。核心思路是:
- 把每种状态抽取成一个独立的类,每个状态类封装该状态下的行为
- 对象(上下文)持有一个"当前状态"的引用,所有行为都委托给当前状态对象
- 状态切换时,只需替换当前状态对象,行为自然就变了
2. 状态模式解决什么问题?
- 对象的行为随状态变化而变化,状态种类多、转换规则复杂
- 代码中出现大量 if/else 或 switch/case 来判断当前状态,然后执行不同逻辑
- 新增状态时需要修改已有的条件判断逻辑,违反开闭原则
- 状态转换规则散落在各处,难以看清全貌,容易出 bug
如果你发现代码里有一个字段叫
state或status,然后到处都在if (state == xxx)做不同的事,就很适合状态模式。
3. 核心结构
3.1 State(抽象状态)
定义一个接口或抽象类,声明在该状态下对象可以执行的行为方法。
3.2 ConcreteState(具体状态)
实现抽象状态接口,封装该状态下的具体行为逻辑。每个具体状态类只关心"我这个状态下该怎么做",以及"该转换到哪个状态"。
3.3 Context(上下文/环境)
持有一个当前状态对象的引用,对外提供业务方法,内部将调用委托给当前状态对象。提供 setState() 方法供状态对象切换状态。
4. 实现思路
- 定义
State接口(声明所有状态下可能的行为方法) - 为每种状态写一个
ConcreteState类(实现该状态下的行为,不允许的操作给出提示或抛异常) - 定义
Context类(持有当前State,所有行为委托给state处理) - 状态转换逻辑放在具体状态类内部(由状态自己决定下一步转到哪个状态)
5. 示例
订单状态流转:待支付 → 已支付 → 已发货 → 已签收
5.1 State:抽象状态
java
public interface OrderState {
/** 支付 */
void pay(OrderContext context);
/** 发货 */
void deliver(OrderContext context);
/** 签收 */
void receive(OrderContext context);
/** 获取当前状态名称 */
String getName();
}
5.2 ConcreteState:具体状态
5.2.1 待支付状态
java
public class PendingPaymentState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("支付成功,订单状态变为【已支付】");
context.setState(new PaidState());
}
@Override
public void deliver(OrderContext context) {
System.out.println("还没付款呢,不能发货!");
}
@Override
public void receive(OrderContext context) {
System.out.println("还没付款呢,不能签收!");
}
@Override
public String getName() {
return "待支付";
}
}
5.2.2 已支付状态
java
public class PaidState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("已经付过款了,不能重复支付!");
}
@Override
public void deliver(OrderContext context) {
System.out.println("发货成功,订单状态变为【已发货】");
context.setState(new DeliveredState());
}
@Override
public void receive(OrderContext context) {
System.out.println("还没发货呢,不能签收!");
}
@Override
public String getName() {
return "已支付";
}
}
5.2.3 已发货状态
java
public class DeliveredState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("已经付过款了,不能重复支付!");
}
@Override
public void deliver(OrderContext context) {
System.out.println("已经发过货了,不能重复发货!");
}
@Override
public void receive(OrderContext context) {
System.out.println("签收成功,订单状态变为【已签收】");
context.setState(new ReceivedState());
}
@Override
public String getName() {
return "已发货";
}
}
5.2.4 已签收状态
java
public class ReceivedState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("订单已完成,不能支付!");
}
@Override
public void deliver(OrderContext context) {
System.out.println("订单已完成,不能发货!");
}
@Override
public void receive(OrderContext context) {
System.out.println("已经签收过了,不能重复签收!");
}
@Override
public String getName() {
return "已签收";
}
}
5.3 Context:上下文
java
public class OrderContext {
private OrderState currentState;
public OrderContext() {
// 初始状态:待支付
this.currentState = new PendingPaymentState();
}
public void setState(OrderState state) {
this.currentState = state;
}
public OrderState getState() {
return currentState;
}
public void pay() {
currentState.pay(this);
}
public void deliver() {
currentState.deliver(this);
}
public void receive() {
currentState.receive(this);
}
}
5.4 Client:客户端使用
java
public class Client {
public static void main(String[] args) {
OrderContext order = new OrderContext();
System.out.println("当前状态:" + order.getState().getName());
// 正常流程:支付 → 发货 → 签收
System.out.println("\n=== 尝试支付 ===");
order.pay();
System.out.println("当前状态:" + order.getState().getName());
System.out.println("\n=== 尝试重复支付 ===");
order.pay();
System.out.println("\n=== 尝试发货 ===");
order.deliver();
System.out.println("当前状态:" + order.getState().getName());
System.out.println("\n=== 尝试签收 ===");
order.receive();
System.out.println("当前状态:" + order.getState().getName());
System.out.println("\n=== 签收后再尝试发货 ===");
order.deliver();
}
}
输出:
当前状态:待支付
=== 尝试支付 ===
支付成功,订单状态变为【已支付】
当前状态:已支付
=== 尝试重复支付 ===
已经付过款了,不能重复支付!
=== 尝试发货 ===
发货成功,订单状态变为【已发货】
当前状态:已发货
=== 尝试签收 ===
签收成功,订单状态变为【已签收】
当前状态:已签收
=== 签收后再尝试发货 ===
订单已完成,不能发货!
会发现:
- 客户端只跟
OrderContext打交道,调用pay()、deliver()、receive(),完全不需要写任何 if/else 判断当前状态 - 每个状态类只关心自己状态下的行为逻辑,职责清晰
- 状态转换规则封装在各个状态类内部,转换路径一目了然
- 如果将来新增一个"退款中"状态,只需新增一个
RefundingState类,在相关状态的方法里加上转换逻辑,不改现有状态类的核心代码
6. 优缺点
6.1 优点
- 消除臃肿的条件判断:把 if/else 或 switch/case 拆分到各个状态类中,代码清晰可读
- 符合单一职责:每个状态类只负责该状态下的行为,职责明确
- 符合开闭原则:新增状态只需新增类,不需要修改已有状态类(大多数情况下)
- 状态转换显式化:转换逻辑在状态类中明确定义,不会散落在各处
6.2 缺点
- 类的数量膨胀:每个状态一个类,状态多了类也多,项目结构变复杂
- 状态类之间可能存在耦合:状态 A 需要知道状态 B 的存在才能转换过去
- 对开闭原则支持有限:如果新增一个行为方法(比如新增"退货"操作),所有状态类都要改
- 过度设计风险:如果状态只有两三个且不太会变,用状态模式反而增加复杂度
7. 和其他模式怎么区分?
7.1 状态模式 vs 策略模式
这两个模式结构几乎一模一样(都是上下文 + 接口 + 多个实现类),但意图完全不同:
- 策略模式:客户端主动选择一个算法/策略,上下文不会自己切换策略
- 状态模式:状态的切换由内部逻辑驱动,客户端通常不直接指定状态,对象自己根据行为结果切换
一句话区分:策略是"我选哪个",状态是"它自己变"。
7.2 状态模式 vs 责任链模式
- 责任链:请求沿链传递,找到能处理的节点就停下,强调的是"谁来处理"
- 状态模式:同一个对象在不同状态下对同一个请求做出不同响应,强调的是"怎么响应"
7.3 状态模式 vs 有限状态机(FSM)
- 状态模式是有限状态机的一种面向对象实现方式
- 简单的状态机可以用枚举 + switch 实现,复杂的状态机用状态模式更优雅
- 如果状态转换规则非常复杂,也可以考虑专门的状态机框架(如 Spring StateMachine)
8. 适用场景
满足以下情况时,状态模式很合适:
- 对象的行为随内部状态改变而改变,且状态种类较多
- 代码中存在大量与状态相关的条件判断(if/else、switch/case)
- 状态之间存在明确的转换规则,且转换逻辑需要清晰可维护
- 状态可能动态增减,希望新增状态时对已有代码影响最小
9. 实际应用
状态模式在 Java 生态中有不少实际应用:
java
// Java 线程状态------天然的状态模式场景
// Thread 的状态:NEW → RUNNABLE → BLOCKED → WAITING → TIMED_WAITING → TERMINATED
// 不同状态下调用 start()、sleep()、wait() 等方法的行为完全不同
Thread thread = new Thread(() -> System.out.println("running"));
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE
常见的状态模式应用:
java.lang.Thread的线程状态管理(NEW、RUNNABLE、BLOCKED、WAITING 等)javax.faces.lifecycle.Lifecycle:JSF 生命周期的不同阶段- Spring StateMachine:Spring 官方的状态机框架,底层就是状态模式的思想
- 工作流引擎(如 Activiti、Flowable):流程实例在不同节点的状态流转
- 游戏开发中角色/NPC 的 AI 状态管理(巡逻、追击、攻击、逃跑)
- TCP 连接状态管理(LISTEN、SYN_SENT、ESTABLISHED、CLOSE_WAIT 等)
10. 总结
状态模式通过"抽象状态 + 多个具体状态 + 上下文委托",将对象在不同状态下的行为封装到独立的状态类中。对象内部状态改变时,行为自动切换,客户端无需关心当前状态和转换逻辑,从而消除了臃肿的条件判断,让状态管理和扩展变得清晰可控。
