【西瓜带你学设计模式 | 第十九期 - 状态模式】状态模式 —— 状态流转与行为切换实现、优缺点与适用场景

文章目录

    • 前言
    • [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. 状态模式解决什么问题?

  1. 对象的行为随状态变化而变化,状态种类多、转换规则复杂
  2. 代码中出现大量 if/else 或 switch/case 来判断当前状态,然后执行不同逻辑
  3. 新增状态时需要修改已有的条件判断逻辑,违反开闭原则
  4. 状态转换规则散落在各处,难以看清全貌,容易出 bug

如果你发现代码里有一个字段叫 statestatus,然后到处都在 if (state == xxx) 做不同的事,就很适合状态模式。


3. 核心结构

3.1 State(抽象状态)

定义一个接口或抽象类,声明在该状态下对象可以执行的行为方法。

3.2 ConcreteState(具体状态)

实现抽象状态接口,封装该状态下的具体行为逻辑。每个具体状态类只关心"我这个状态下该怎么做",以及"该转换到哪个状态"。

3.3 Context(上下文/环境)

持有一个当前状态对象的引用,对外提供业务方法,内部将调用委托给当前状态对象。提供 setState() 方法供状态对象切换状态。


4. 实现思路

  1. 定义 State 接口(声明所有状态下可能的行为方法)
  2. 为每种状态写一个 ConcreteState 类(实现该状态下的行为,不允许的操作给出提示或抛异常)
  3. 定义 Context 类(持有当前 State,所有行为委托给 state 处理)
  4. 状态转换逻辑放在具体状态类内部(由状态自己决定下一步转到哪个状态)

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 优点

  1. 消除臃肿的条件判断:把 if/else 或 switch/case 拆分到各个状态类中,代码清晰可读
  2. 符合单一职责:每个状态类只负责该状态下的行为,职责明确
  3. 符合开闭原则:新增状态只需新增类,不需要修改已有状态类(大多数情况下)
  4. 状态转换显式化:转换逻辑在状态类中明确定义,不会散落在各处

6.2 缺点

  1. 类的数量膨胀:每个状态一个类,状态多了类也多,项目结构变复杂
  2. 状态类之间可能存在耦合:状态 A 需要知道状态 B 的存在才能转换过去
  3. 对开闭原则支持有限:如果新增一个行为方法(比如新增"退货"操作),所有状态类都要改
  4. 过度设计风险:如果状态只有两三个且不太会变,用状态模式反而增加复杂度

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. 总结

状态模式通过"抽象状态 + 多个具体状态 + 上下文委托",将对象在不同状态下的行为封装到独立的状态类中。对象内部状态改变时,行为自动切换,客户端无需关心当前状态和转换逻辑,从而消除了臃肿的条件判断,让状态管理和扩展变得清晰可控。

相关推荐
Han.miracle2 小时前
微服务注册中心实操:Eureka+Zookeeper对比+CAP定理详解
java·spring boot·spring
Pomelo_刘金2 小时前
Rust:新版本 1.91.1 版本修复解析
后端·rust·编程语言
llm大模型算法工程师weng2 小时前
Java面试核心突破:面向对象与设计模式
java·设计模式·面试
Pomelo_刘金2 小时前
Rust:新版本 1.92.0 Never 类型更新
后端·rust·编程语言
Pomelo_刘金2 小时前
Rust:新版本 1.94.0 深度解析
后端·rust·编程语言
weixin_520649872 小时前
xml json ini 文件语法
xml·java·json
user_admin_god2 小时前
AI编码OpenCode入门到入神
java·人工智能
Pomelo_刘金2 小时前
Rust 1.94.1 版本修复解析
后端·rust·编程语言
都说名字长不会被发现2 小时前
多服务节点数据修正方案设计与实现
java·事务性发件箱·数据修正