什么是状态模式?应用场景有哪些?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 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/

面试考察点

面试官提出这个问题,主要希望考察以下几个层次:

  1. 对设计模式的理解深度:你是否能清晰地阐述一个经典设计模式的核心思想、结构和目的。
  2. 辨别与解决实际问题的能力:你是否能意识到,何时使用 if-elseswitch-case 判断状态会导致代码维护性变差,以及状态模式如何优雅地解决这一问题。这不仅仅是 “知道”,更是 “理解其必要性”。
  3. 面向对象设计原则的掌握:你是否能识别出状态模式如何体现了 开闭原则(对扩展开放,对修改封闭)单一职责原则
  4. 实际应用经验:你是否能将理论应用于实际,列举出真正适合使用该模式的业务场景,而不是生搬硬套。
  5. 实现细节的关注:你是否了解实现时的一些关键点,例如状态转换由谁驱动、状态对象是否需要共享等。

核心答案

状态模式是一种行为设计模式,它允许一个对象在其内部状态改变时改变它的行为,使得这个对象看起来好像修改了它的类。

其核心思想是:将与特定状态相关的行为封装到独立的状态类中,并让原对象(上下文)将具体行为委托给当前持有的状态对象。这样,当上下文的状态改变时,只需切换到对应状态类的实例,其行为便会随之自动改变。

主要应用场景包括:订单状态流转(如待支付、已支付、已发货)、工作流引擎(如审批流程)、游戏中的角色状态(如正常、中毒、眩晕)、线程状态管理、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-elseswitch 在上下文中判断状态,代码会迅速膨胀为一个难以维护的 “面条代码”。每次增加新状态或修改某个状态的行为,都可能需要修改这个庞大的条件判断块,违反了开闭原则。

状态模式通过多态消除了这些条件判断,将每个状态的行为局部化到各自的类中,新增状态只需添加新的状态类,修改行为只需修改特定状态类,使得代码更清晰、可维护性更强。

最佳实践

  • 状态转换的驱动:通常推荐由上下文(OrderContext)来负责状态转换(通过 setState 方法),这样转换逻辑更集中、可控。
  • 状态对象的创建:如果状态类是无状态的(不包含成员变量),可以设计成单例(如使用枚举),以避免重复创建对象。
  • 初始化和清理:可以考虑在状态接口中增加 onEntry()onExit() 方法,用于处理进入或离开某个状态时的特定逻辑。

常见误区

  • 滥用模式:状态数量很少(如只有2-3个)且行为稳定时,使用 if-else 可能更简单直接。引入状态模式会额外增加类的数量,带来一定复杂度。
  • 混淆与策略模式:两者结构相似,但目的不同。策略模式的 “策略” 是客户主动选择和注入的,策略之间通常是平等的、可互换的算法,其目的是灵活地改变对象的算法状态模式的 “状态” 是上下文内部驱动的,状态之间往往存在固定的转换路径,其目的是管理对象状态依赖的行为

总结

状态模式通过将对象的状态和行为绑定到独立的状态类中,优雅地解决了对象在多种状态间切换时行为复杂、条件判断冗余的问题,是管理复杂状态机的理想选择,尤其适用于状态流转清晰、行为各异的业务场景。