在软件开发中,我们经常会遇到对象的行为随着其内部状态的变化而变化的情况。例如,一个订单可能处于"待支付"、"已支付"、"已发货"或"已完成"等不同状态,每个状态下订单的操作逻辑可能完全不同。如果直接在代码中使用大量的if-else
或switch-case
语句来判断状态,会导致代码臃肿、难以维护,并且违反开闭原则(OCP)。
状态模式(State Pattern)提供了一种优雅的解决方案,它允许对象在运行时根据内部状态改变其行为,而不必在代码中显式地检查状态。本文将深入探讨状态模式的核心概念、实现方式、优缺点以及实际应用场景,并通过代码示例帮助读者更好地理解该模式。
1. 什么是状态模式?
1.1 定义
状态模式是一种行为型设计模式,它允许一个对象在其内部状态改变时改变其行为,使得对象看起来像是修改了它的类。
1.2 核心思想
-
将状态抽象成独立的类,每个状态类负责处理该状态下的行为。
-
上下文(Context)对象持有一个状态对象的引用,并将行为委托给当前状态对象。
-
状态转换由状态类自身或上下文类控制,而不是在业务逻辑中硬编码。
1.3 适用场景
-
对象的行为依赖于它的状态,并且需要在运行时动态改变行为。
-
代码中包含大量与对象状态相关的条件分支语句,导致逻辑复杂。
-
状态转换逻辑较复杂,且未来可能新增状态。
2. 状态模式的结构
状态模式主要由以下几个角色组成:
角色 | 作用 |
---|---|
Context(上下文) | 定义客户端接口,维护当前状态对象。 |
State(抽象状态) | 定义状态接口,封装与特定状态相关的行为。 |
ConcreteState(具体状态) | 实现状态接口,处理该状态下的行为逻辑,并可触发状态转换。 |
2.1 UML 类图
┌─────────────┐ ┌─────────────┐
│ Context │ │ State │
├─────────────┤ ├─────────────┤
│ -state:State│<>---->│ +handle() │
├─────────────┤ └─────────────┘
│ +request() │ ▲
└─────────────┘ │
│
┌──────────────┴──────────────┐
│ │
┌─────────────┐ ┌─────────────┐
│ConcreteStateA│ │ConcreteStateB│
├─────────────┤ ├─────────────┤
│ +handle() │ │ +handle() │
└─────────────┘ └─────────────┘
3. 代码示例:订单状态管理
假设我们有一个订单系统,订单可能处于以下状态:
-
待支付(Pending)
-
已支付(Paid)
-
已发货(Shipped)
-
已完成(Completed)
3.1 定义状态接口
public interface OrderState {
void next(Order order);
void prev(Order order);
void printStatus();
}
3.2 实现具体状态类
// 待支付状态
public class Pending implements OrderState {
@Override
public void next(Order order) {
order.setState(new Paid());
}
@Override
public void prev(Order order) {
System.out.println("订单尚未支付,无法回退。");
}
@Override
public void printStatus() {
System.out.println("订单状态:待支付");
}
}
// 已支付状态
public class Paid implements OrderState {
@Override
public void next(Order order) {
order.setState(new Shipped());
}
@Override
public void prev(Order order) {
order.setState(new Pending());
}
@Override
public void printStatus() {
System.out.println("订单状态:已支付");
}
}
// 已发货状态
public class Shipped implements OrderState {
@Override
public void next(Order order) {
order.setState(new Completed());
}
@Override
public void prev(Order order) {
order.setState(new Paid());
}
@Override
public void printStatus() {
System.out.println("订单状态:已发货");
}
}
// 已完成状态
public class Completed implements OrderState {
@Override
public void next(Order order) {
System.out.println("订单已完成,无法继续推进。");
}
@Override
public void prev(Order order) {
order.setState(new Shipped());
}
@Override
public void printStatus() {
System.out.println("订单状态:已完成");
}
}
3.3 定义上下文类(Order)
public class Order {
private OrderState state;
public Order() {
this.state = new Pending(); // 初始状态:待支付
}
public void setState(OrderState state) {
this.state = state;
}
public void nextState() {
state.next(this);
}
public void prevState() {
state.prev(this);
}
public void printStatus() {
state.printStatus();
}
}
3.4 客户端测试
public class Client {
public static void main(String[] args) {
Order order = new Order();
order.printStatus(); // 待支付
order.nextState(); // 推进到已支付
order.printStatus(); // 已支付
order.nextState(); // 推进到已发货
order.printStatus(); // 已发货
order.prevState(); // 回退到已支付
order.printStatus(); // 已支付
order.nextState(); // 推进到已发货
order.nextState(); // 推进到已完成
order.printStatus(); // 已完成
}
}
输出结果:
订单状态:待支付
订单状态:已支付
订单状态:已发货
订单状态:已支付
订单状态:已发货
订单状态:已完成
4. 状态模式的优缺点
4.1 优点
✅ 符合单一职责原则 :每个状态类只负责自己的行为逻辑。
✅ 符合开闭原则(OCP) :新增状态时无需修改现有代码。
✅ 减少条件分支 :避免复杂的if-else
或switch-case
逻辑。
✅ 状态转换逻辑清晰:状态转换由状态类自身控制,易于维护。
4.2 缺点
❌ 类数量增加 :每个状态都需要一个单独的类,可能导致类膨胀。
❌ 状态转换逻辑分散 :如果状态转换逻辑复杂,可能难以追踪。
❌ 可能引入循环依赖:状态类可能依赖上下文类,反之亦然。
5. 状态模式的实际应用
5.1 电商订单系统
-
订单状态流转:
待支付 → 已支付 → 已发货 → 已完成
。 -
不同状态下,订单的操作(如取消、退款)逻辑不同。
5.2 游戏角色状态
-
角色可能有
站立
、奔跑
、跳跃
、攻击
等状态。 -
不同状态下,角色的动画、移动速度、碰撞检测等行为不同。
5.3 线程状态管理
-
Java 线程的状态:
NEW
、RUNNABLE
、BLOCKED
、WAITING
、TERMINATED
。 -
不同状态下,线程的调度策略不同。
5.4 UI 组件状态
-
按钮的状态:
正常
、悬停
、按下
、禁用
。 -
不同状态下,按钮的样式和交互逻辑不同。
6. 状态模式 vs. 策略模式
状态模式和策略模式在结构上非常相似,但它们的设计意图不同:
对比点 | 状态模式 | 策略模式 |
---|---|---|
目的 | 让对象在不同状态下改变行为 | 让客户端选择不同的算法 |
状态转换 | 状态类可以自动切换 | 策略通常由客户端设置 |
依赖关系 | 状态类可能依赖上下文 | 策略类通常独立 |
7. 总结
状态模式是一种强大的设计模式,特别适用于对象行为随状态变化而变化的场景。它通过将状态逻辑分散到不同的类中,使得代码更加清晰、可扩展。尽管它可能增加类的数量,但在复杂状态管理场景下,它能显著提升代码的可维护性。
适用场景总结:
-
对象有多个状态,且行为随状态变化。
-
代码中有大量状态相关的条件分支。
-
未来可能新增状态,需要灵活扩展。
推荐使用场景:
✅ 订单状态管理
✅ 游戏角色状态切换
✅ 线程/进程状态管理
✅ UI 组件交互状态