什么是状态模式?应用场景有哪些?
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
面试官提出这个问题,主要希望考察以下几个层次:
- 对设计模式的理解深度:你是否能清晰地阐述一个经典设计模式的核心思想、结构和目的。
- 辨别与解决实际问题的能力:你是否能意识到,何时使用
if-else或switch-case判断状态会导致代码维护性变差,以及状态模式如何优雅地解决这一问题。这不仅仅是 “知道”,更是 “理解其必要性”。 - 面向对象设计原则的掌握:你是否能识别出状态模式如何体现了 开闭原则(对扩展开放,对修改封闭) 和 单一职责原则。
- 实际应用经验:你是否能将理论应用于实际,列举出真正适合使用该模式的业务场景,而不是生搬硬套。
- 实现细节的关注:你是否了解实现时的一些关键点,例如状态转换由谁驱动、状态对象是否需要共享等。
核心答案
状态模式是一种行为设计模式,它允许一个对象在其内部状态改变时改变它的行为,使得这个对象看起来好像修改了它的类。
其核心思想是:将与特定状态相关的行为封装到独立的状态类中,并让原对象(上下文)将具体行为委托给当前持有的状态对象。这样,当上下文的状态改变时,只需切换到对应状态类的实例,其行为便会随之自动改变。
主要应用场景包括:订单状态流转(如待支付、已支付、已发货)、工作流引擎(如审批流程)、游戏中的角色状态(如正常、中毒、眩晕)、线程状态管理、TCP 连接状态(如建立连接、监听、关闭) 等,任何对象行为依赖于其状态,且状态转换复杂、行为多样的场景都适用。
深度解析
原理/机制
状态模式基于有限状态机的概念,其核心角色有:
- 上下文:持有当前状态对象的引用,并定义了客户感兴趣的接口。它是状态转换的载体。
- 抽象状态:定义一个接口,用以封装与上下文的一个特定状态相关的行为。
- 具体状态:实现抽象状态接口,每一个具体状态类对应上下文的一种状态,并负责在该状态下处理上下文的行为请求。
状态转换可以发生在上下文内部,也可以由具体状态类来触发。前者更集中,后者更分散但每个状态类职责更完整。
代码示例
以一个简化的订单状态为例:
// 1. 抽象状态
interface OrderState {
void handlePayment(OrderContext context);
void ship(OrderContext context);
void cancel(OrderContext context);
}
// 2. 具体状态类
class UnpaidState implements OrderState {
@Override
public void handlePayment(OrderContext context) {
System.out.println("支付成功。");
context.setState(new PaidState()); // 状态转换
}
@Override
public void ship(OrderContext context) {
System.out.println("【失败】订单未支付,无法发货。");
}
@Override
public void cancel(OrderContext context) {
System.out.println("订单已取消。");
context.setState(new CancelledState());
}
}
class PaidState implements OrderState {
@Override
public void handlePayment(OrderContext context) {
System.out.println("【警告】订单已支付,请勿重复支付。");
}
@Override
public void ship(OrderContext context) {
System.out.println("发货成功,等待收货。");
context.setState(new ShippedState());
}
@Override
public void cancel(OrderContext context) {
System.out.println("取消已支付订单,需走退款流程。");
context.setState(new RefundingState());
}
}
// 3. 上下文
class OrderContext {
private OrderState currentState;
public OrderContext() {
this.currentState = new UnpaidState(); // 初始状态
}
public void setState(OrderState state) {
this.currentState = state;
}
// 将行为委托给当前状态对象
public void pay() {
currentState.handlePayment(this);
}
public void ship() {
currentState.ship(this);
}
public void cancel() {
currentState.cancel(this);
}
}
// 4. 客户端使用
public class Client {
public static void main(String[] args) {
OrderContext order = new OrderContext();
order.ship(); // 输出:【失败】订单未支付,无法发货。
order.pay(); // 输出:支付成功。
order.ship(); // 输出:发货成功,等待收货。
order.pay(); // 输出:【警告】订单已支付,请勿重复支付。
}
}
对比分析(状态模式 vs. 条件语句)
如果使用传统的 if-else 或 switch 在上下文中判断状态,代码会迅速膨胀为一个难以维护的 “面条代码”。每次增加新状态或修改某个状态的行为,都可能需要修改这个庞大的条件判断块,违反了开闭原则。
状态模式通过多态消除了这些条件判断,将每个状态的行为局部化到各自的类中,新增状态只需添加新的状态类,修改行为只需修改特定状态类,使得代码更清晰、可维护性更强。
最佳实践
- 状态转换的驱动:通常推荐由上下文(
OrderContext)来负责状态转换(通过setState方法),这样转换逻辑更集中、可控。 - 状态对象的创建:如果状态类是无状态的(不包含成员变量),可以设计成单例(如使用枚举),以避免重复创建对象。
- 初始化和清理:可以考虑在状态接口中增加
onEntry()和onExit()方法,用于处理进入或离开某个状态时的特定逻辑。
常见误区
- 滥用模式:状态数量很少(如只有2-3个)且行为稳定时,使用
if-else可能更简单直接。引入状态模式会额外增加类的数量,带来一定复杂度。 - 混淆与策略模式:两者结构相似,但目的不同。策略模式的 “策略” 是客户主动选择和注入的,策略之间通常是平等的、可互换的算法,其目的是灵活地改变对象的算法。状态模式的 “状态” 是上下文内部驱动的,状态之间往往存在固定的转换路径,其目的是管理对象状态依赖的行为。
总结
状态模式通过将对象的状态和行为绑定到独立的状态类中,优雅地解决了对象在多种状态间切换时行为复杂、条件判断冗余的问题,是管理复杂状态机的理想选择,尤其适用于状态流转清晰、行为各异的业务场景。