这是 2020 年 8 月整理的设计模式专题第十四篇。状态模式是行为型模式里较难的一篇——笔记只留下了简短定义,本文补全了完整示例。它的核心是:对象的行为由内部状态决定,状态一变,行为就变。
简单说明
状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为。
- 适用于一个对象有多种状态且状态可以相互转换的场景;
- 将各种状态的转换逻辑分散到 State 的子类中,减少之间的相互依赖;
- 在加入新的状态时非常简单。
传统状态机(if-else 堆砌): 状态模式:
if (state == 待支付) { 状态接口 <── 待支付状态
行为A ├── 已支付状态
} else if (state == 已支付) { └── 已发货状态
行为B (每个状态是一个类,
} else if (state == 已发货) { 状态迁移由状态类自己驱动)
行为C
}
为什么需要状态模式
订单的状态流转是典型场景:待支付 → 已支付 → 已发货 → 已完成。用 if-else 写:
public void handle(Order order) {
if ("待支付".equals(order.getState())) {
// 待支付:可以支付
} else if ("已支付".equals(order.getState())) {
// 已支付:可以发货
} else if ("已发货".equals(order.getState())) {
// 已发货:可以确认收货
}
// 每加一个状态,都要改这个方法 —— 违反开闭原则
}
问题:所有状态的行为堆在一个方法里,新增状态就要改这段逻辑,且状态越多方法越臃肿。状态模式把”每个状态的行为”抽成独立的类。
代码实现
以订单状态流转为例:
/**
* 描述:订单上下文(持有当前状态)
*/
public class Order {
private OrderState state = new PendingPayState(); // 初始:待支付
public void setState(OrderState state) {
this.state = state;
}
public void pay() {
state.pay(this); // 行为委托给当前状态对象
}
public void ship() {
state.ship(this);
}
public void confirm() {
state.confirm(this);
}
}
/**
* 描述:状态接口:定义每种状态下各操作的行为
*/
public interface OrderState {
void pay(Order order);
void ship(Order order);
void confirm(Order order);
}
/**
* 描述:待支付状态
*/
public class PendingPayState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("支付成功,订单变为已支付");
order.setState(new PaidState()); // 状态迁移:自己驱动转换
}
@Override
public void ship(Order order) {
System.out.println("未支付不能发货");
}
@Override
public void confirm(Order order) {
System.out.println("未支付不能确认收货");
}
}
/**
* 描述:已支付状态
*/
public class PaidState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("已支付,请勿重复支付");
}
@Override
public void ship(Order order) {
System.out.println("发货成功,订单变为已发货");
order.setState(new ShippedState());
}
@Override
public void confirm(Order order) {
System.out.println("未发货不能确认收货");
}
}
/**
* 描述:已发货状态
*/
public class ShippedState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("已发货,无需支付");
}
@Override
public void ship(Order order) {
System.out.println("已发货,请勿重复发货");
}
@Override
public void confirm(Order order) {
System.out.println("确认收货,订单完成");
order.setState(new FinishedState());
}
}
/**
* 描述:已完成状态(终态)
*/
public class FinishedState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("订单已完成");
}
@Override
public void ship(Order order) {
System.out.println("订单已完成");
}
@Override
public void confirm(Order order) {
System.out.println("订单已完成");
}
}
public class StateTest {
public static void main(String[] args) {
Order order = new Order();
order.pay(); // 待支付状态下调用 → 支付成功,转为已支付
order.ship(); // 已支付状态下调用 → 发货成功,转为已发货
order.confirm(); // 已发货状态下调用 → 确认收货,完成
order.pay(); // 已完成状态下调用 → "订单已完成"(非法操作被状态拦截)
}
}
关键点:
- Context(Order)持有状态对象,所有操作委托给当前状态;
- 每个状态一个类,
pay/ship/confirm在各自状态类里实现”这个状态下该怎么做”; - 状态迁移由状态类自己驱动:
order.setState(new PaidState())—— 合法操作让状态前进,非法操作被拦截并提示; - 新增状态:加一个状态类 + 调整迁移逻辑,不用改所有 if-else。
状态模式 vs 策略模式
两者结构相似(都是”上下文 + 接口 + 多个实现类”),但意图完全不同:
| 对比项 | 状态模式 | 策略模式 |
|---|---|---|
| 关注点 | 对象内部状态的变化驱动行为变化 | 为同一业务选择不同算法 |
| 状态切换 | 状态之间自动流转(状态类自己驱动) | 策略由客户端主动选择,互不相识 |
| 行为 | 同一个操作在不同状态下行为不同 | 不同策略实现同一接口的不同算法 |
| 类比 | 人有情绪,心情不同反应不同 | 出行选公交/打车,算法不同目标相同 |
一句话区分:策略模式是”换算法”,状态模式是”换身份”——状态模式里对象当前的行为完全由它处于哪个状态决定。
适用场景与优缺点
适用场景:
- 对象的行为依赖其状态,且状态可相互转换;
- 状态转换逻辑复杂,if-else 难以维护;
- 代码中包含大量与对象状态有关的条件语句。
优点:
- 状态转换逻辑分散到各状态类,职责清晰;
- 消除庞大的条件分支语句;
- 新增状态简单,符合开闭原则;
- 状态转换规则集中,便于维护。
缺点:
- 状态类数量多,系统类膨胀;
- 状态转换逻辑分散在各状态类中,全局流转不易一眼看清;
- 如果状态转换规则频繁变动,改动面较大。
现实中的应用
| 场景 | 说明 |
|---|---|
| 订单状态机 | 待支付→已支付→已发货→已完成 |
| TCP 连接状态 | LISTEN→SYN_SENT→ESTABLISHED→CLOSE_WAIT |
| 工作流引擎 | 审批状态流转 |
| 播放器状态 | 播放/暂停/停止,同一按键在不同状态下行为不同 |
小结
- 状态模式允许对象在内部状态改变时改变行为,行为由当前状态决定。
- 结构:Context 持有状态对象 + 状态接口 + 各状态实现类;状态迁移由状态类自己驱动。
- 与策略模式的区别:策略换算法,状态换身份。
- 优点:消除大段状态 if-else,新增状态简单,符合开闭原则。
- 缺点:状态类数量多,全局流转不直观。
- 一句话:人在什么状态,就做什么反应——状态变了,行为跟着变。