设计模式-状态模式

文章目录

  • 一、概述
    • [1.1 结构与角色](#1.1 结构与角色)
    • [1.2 适用场景](#1.2 适用场景)
  • 二、实现方式
    • [2.1 基础实现](#2.1 基础实现)
    • [2.2 状态模式 vs if-else 对比](#2.2 状态模式 vs if-else 对比)
  • [三、状态模式 vs 策略模式](#三、状态模式 vs 策略模式)
    • [3.1 核心区别](#3.1 核心区别)
    • [3.2 代码对比](#3.2 代码对比)
    • [3.3 选型指南](#3.3 选型指南)
  • 四、总结

一、概述

在软件开发中,经常会遇到这样的场景:一个对象的行为会随着其内部状态 的改变而变化。例如,订单从"待支付"→"已支付"→"已发货"→"已完成"的流转过程中,每个状态下允许的操作都不同;电梯在"停止""上行""下行"三种状态下,对"开门""关门""上行""下行"指令的响应也不同。如果使用大量的 if-elseswitch-case 来管理状态切换,代码会变得臃肿、晦涩、难以维护

java 复制代码
// 反例:用 if-else 管理状态------代码会越来越难维护
public void handleOrder(String action) {
    if ("PENDING".equals(state)) {
        if ("pay".equals(action)) {
            state = "PAID";
        }
        if ("cancel".equals(action)) {
            state = "CANCELLED";
        }
    } else if ("PAID".equals(state)) {
        if ("ship".equals(action)) {
            state = "SHIPPED";
        }
        if ("refund".equals(action)) {
            state = "REFUNDING";
        }
    } else if ("SHIPPED".equals(state)) {
        if ("confirm".equals(action)) {
            state = "COMPLETED";
        }
    }
    // ... 随着状态增多,分支爆炸
}

状态模式(State Pattern)正是为了解决这个问题而诞生的------它允许对象在其内部状态改变时改变它的行为,使得对象看起来似乎修改了它的类。状态模式将与特定状态相关的行为封装到独立的状态类中,并通过状态对象内部自动完成切换,消除了臃肿的条件判断。

生活中的状态模式例子:

  • 自动售货机:有"无币""有币""出货""售罄"四种状态,在不同状态下投币、退币、出货的响应不同
  • 电梯系统:在"停止""上行""下行"三种状态下,对开门、关门、目的地按钮的响应不同,状态自动切换
  • 文档审批流程:文档从"草稿"→"已提交"→"审核中"→"已批准"/"已驳回",每个状态下可执行的操作不同
  • TCP 连接:连接在 CLOSED、LISTEN、SYN_SENT、ESTABLISHED 等状态间自动切换,状态决定了对网络事件的响应

核心:允许对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。将与特定状态相关的行为封装到独立的状态类中,让状态转换由状态对象内部自动完成。

1.1 结构与角色

状态模式包含以下角色:
持有当前状态
实现
实现
实现
委托行为
切换状态
切换状态
切换状态
Client 客户端
Context 上下文
State 抽象状态
ConcreteStateA 具体状态A
ConcreteStateB 具体状态B
ConcreteStateC 具体状态C

  • State(抽象状态):定义所有具体状态的公共接口,声明在特定状态下可执行的行为
  • ConcreteState(具体状态):实现抽象状态接口,封装与该状态相关的行为,并在需要时切换上下文的状态
  • Context(上下文):持有当前状态的引用,将客户端请求委托给当前状态对象处理,提供切换状态的方法供具体状态调用
  • Client(客户端):创建上下文和初始状态对象,通过上下文接口触发状态行为

与策略模式的关键区别:状态模式的转换是自动的、由状态对象内部控制的,而策略模式的切换是由客户端主动选择的。

1.2 适用场景

  • 对象的行为依赖于它的状态,且必须在运行时根据状态改变它的行为
  • 代码中包含大量与对象状态相关的条件判断(if-else / switch-case
  • 状态数量较多且状态转换逻辑复杂,条件判断会导致代码臃肿
  • 需要将状态转换逻辑和状态对应的行为分离,使其独立变化

二、实现方式

状态模式的核心实现思路是:将不同状态下的行为分别封装为独立的状态类,它们实现同一个状态接口,上下文通过组合持有当前状态的引用,将行为的执行委托给当前状态对象。状态的切换由状态对象内部触发,通过上下文提供的 setState 方法完成。

以"订单状态流转"为例,订单从"待支付"→"已支付"→"已发货"→"已完成",不同状态下可执行的操作不同:
支付 pay
取消 cancel
发货 ship
退款 refund
确认收货 confirm
退款完成
待支付 PENDING
已支付 PAID
已取消 CANCELLED
已发货 SHIPPED
退款中 REFUNDING
已完成 COMPLETED
已退款 REFUNDED

2.1 基础实现

(1)抽象状态接口------订单状态

java 复制代码
/**
 * 抽象状态:订单状态
 * 定义所有状态下共有的行为
 */
public interface OrderState {

    /**
     * 支付
     */
    void pay(OrderContext context);

    /**
     * 发货
     */
    void ship(OrderContext context);

    /**
     * 确认收货
     */
    void confirm(OrderContext context);

    /**
     * 取消订单
     */
    void cancel(OrderContext context);

    /**
     * 获取状态名称
     *
     * @return 状态名称
     */
    String getStateName();
}

(2)上下文------订单上下文

java 复制代码
/**
 * 上下文:订单上下文
 * 持有当前状态的引用,将行为委托给当前状态对象
 * 提供 setState() 方法供具体状态类在内部切换状态
 */
public class OrderContext {

    /** 当前状态 */
    private OrderState currentState;

    /** 订单号 */
    private final String orderId;

    public OrderContext(String orderId) {
        this.orderId = orderId;
        // 初始状态:待支付
        this.currentState = new PendingState();
        System.out.println("订单 " + orderId + " 已创建,当前状态:" + currentState.getStateName());
    }

    /**
     * 切换状态(由具体状态类内部调用)
     *
     * @param newState 新状态
     */
    public void setState(OrderState newState) {
        System.out.println("订单 " + orderId + " 状态变更:" + this.currentState.getStateName()
                + " → " + newState.getStateName());
        this.currentState = newState;
    }

    // --- 将行为委托给当前状态 ---

    public void pay() {
        currentState.pay(this);
    }

    public void ship() {
        currentState.ship(this);
    }

    public void confirm() {
        currentState.confirm(this);
    }

    public void cancel() {
        currentState.cancel(this);
    }

    public String getCurrentStateName() {
        return currentState.getStateName();
    }
}

(3)具体状态------待支付状态

java 复制代码
/**
 * 具体状态:待支付
 * 在该状态下可以支付或取消,不能发货或确认收货
 */
public class PendingState implements OrderState {

    @Override
    public void pay(OrderContext context) {
        System.out.println("支付成功!");
        // 切换到已支付状态
        context.setState(new PaidState());
    }

    @Override
    public void ship(OrderContext context) {
        System.out.println("【错误】订单尚未支付,无法发货");
    }

    @Override
    public void confirm(OrderContext context) {
        System.out.println("【错误】订单尚未支付,无法确认收货");
    }

    @Override
    public void cancel(OrderContext context) {
        System.out.println("订单已取消");
        // 切换到已取消状态
        context.setState(new CancelledState());
    }

    @Override
    public String getStateName() {
        return "待支付";
    }
}

(4)具体状态------已支付状态

java 复制代码
/**
 * 具体状态:已支付
 * 在该状态下可以发货或退款,不能支付或确认收货
 */
public class PaidState implements OrderState {

    @Override
    public void pay(OrderContext context) {
        System.out.println("【错误】订单已支付,请勿重复支付");
    }

    @Override
    public void ship(OrderContext context) {
        System.out.println("发货成功!");
        // 切换到已发货状态
        context.setState(new ShippedState());
    }

    @Override
    public void confirm(OrderContext context) {
        System.out.println("【错误】订单尚未发货,无法确认收货");
    }

    @Override
    public void cancel(OrderContext context) {
        System.out.println("订单已退款");
        // 切换到退款中状态
        context.setState(new RefundingState());
    }

    @Override
    public String getStateName() {
        return "已支付";
    }
}

(5)具体状态------已发货状态

java 复制代码
/**
 * 具体状态:已发货
 * 在该状态下只能确认收货,不能支付、发货或取消
 */
public class ShippedState implements OrderState {

    @Override
    public void pay(OrderContext context) {
        System.out.println("【错误】订单已发货,无需支付");
    }

    @Override
    public void ship(OrderContext context) {
        System.out.println("【错误】订单已发货,请勿重复发货");
    }

    @Override
    public void confirm(OrderContext context) {
        System.out.println("确认收货成功!订单已完成");
        // 切换到已完成状态
        context.setState(new CompletedState());
    }

    @Override
    public void cancel(OrderContext context) {
        System.out.println("【错误】订单已发货,无法取消,请联系客服");
    }

    @Override
    public String getStateName() {
        return "已发货";
    }
}

(6)终态------已完成、已取消、退款中

java 复制代码
/**
 * 具体状态:已完成(终态)
 */
public class CompletedState implements OrderState {

    @Override
    public void pay(OrderContext context) {
        System.out.println("【错误】订单已完成,无法操作");
    }

    @Override
    public void ship(OrderContext context) {
        System.out.println("【错误】订单已完成,无法操作");
    }

    @Override
    public void confirm(OrderContext context) {
        System.out.println("【错误】订单已完成,无需重复确认");
    }

    @Override
    public void cancel(OrderContext context) {
        System.out.println("【错误】订单已完成,无法取消");
    }

    @Override
    public String getStateName() {
        return "已完成";
    }
}

/**
 * 具体状态:已取消(终态)
 */
public class CancelledState implements OrderState {

    @Override
    public void pay(OrderContext context) {
        System.out.println("【错误】订单已取消,无法支付");
    }

    @Override
    public void ship(OrderContext context) {
        System.out.println("【错误】订单已取消,无法发货");
    }

    @Override
    public void confirm(OrderContext context) {
        System.out.println("【错误】订单已取消,无法确认收货");
    }

    @Override
    public void cancel(OrderContext context) {
        System.out.println("【错误】订单已取消,请勿重复操作");
    }

    @Override
    public String getStateName() {
        return "已取消";
    }
}

/**
 * 具体状态:退款中(终态)
 */
public class RefundingState implements OrderState {

    @Override
    public void pay(OrderContext context) {
        System.out.println("【错误】订单退款中,无法支付");
    }

    @Override
    public void ship(OrderContext context) {
        System.out.println("【错误】订单退款中,无法发货");
    }

    @Override
    public void confirm(OrderContext context) {
        System.out.println("【错误】订单退款中,无法确认收货");
    }

    @Override
    public void cancel(OrderContext context) {
        System.out.println("【错误】订单退款中,请勿重复操作");
    }

    @Override
    public String getStateName() {
        return "退款中";
    }
}

(7)客户端调用

java 复制代码
public class StatePatternDemo {
    public static void main(String[] args) {
        OrderContext order = new OrderContext("ORD-20240519-001");

        System.out.println("\n--- 正常流程 ---");
        order.pay();      // 待支付 → 已支付
        order.ship();     // 已支付 → 已发货
        order.confirm();  // 已发货 → 已完成

        System.out.println("\n--- 异常操作测试 ---");
        OrderContext order2 = new OrderContext("ORD-20240519-002");
        order2.ship();    // 错误:尚未支付
        order2.cancel();  // 待支付 → 已取消
        order2.pay();     // 错误:已取消
    }
}

输出结果:

复制代码
订单 ORD-20240519-001 已创建,当前状态:待支付

--- 正常流程 ---
支付成功!
订单 ORD-20240519-001 状态变更:待支付 → 已支付
发货成功!
订单 ORD-20240519-001 状态变更:已支付 → 已发货
确认收货成功!订单已完成
订单 ORD-20240519-001 状态变更:已发货 → 已完成

--- 异常操作测试 ---
订单 ORD-20240519-002 已创建,当前状态:待支付
【错误】订单尚未支付,无法发货
订单已取消
订单 ORD-20240519-002 状态变更:待支付 → 已取消
【错误】订单已取消,无法支付

关键点 :每个状态类只关心自己状态下的合法行为,状态切换由状态对象内部调用 context.setState() 完成,客户端无需关心状态转换逻辑。新增状态时只需新增一个实现类,无需修改已有状态类。

2.2 状态模式 vs if-else 对比

以 6 种订单状态为例:

对比维度 if-else 实现 状态模式
代码结构 一个类中集中所有状态逻辑 每个状态独立封装为一个类
新增状态 修改已有类,在所有分支方法中加 else-if 新增一个状态类,无需修改已有代码
状态转换 直接修改状态字段,容易遗漏 状态对象内部通过 setState 切换,逻辑清晰
代码可读性 分支嵌套让逻辑难以理解 每个状态类职责单一,一目了然
符合开闭原则 是(对扩展开放,对修改关闭)
状态数量 少时简单 每增加一个状态就增加一个类

三、状态模式 vs 策略模式

状态模式和策略模式在结构上非常相似------都是通过组合持有一个接口引用来委托行为。但它们的意图使用方式有着本质区别:
状态模式
持有当前状态
实现
实现
实现
自动切换
自动切换
自动切换
Client 客户端
Context 上下文
State 抽象状态
状态A
状态B
状态C
策略模式
主动选择
持有
实现
实现
实现
Client 客户端
Context 上下文
Strategy 策略接口
策略A
策略B
策略C

3.1 核心区别

对比维度 策略模式 状态模式
意图 定义一系列可互换的算法,让客户端任选其一 根据内部状态自动切换行为
切换方式 客户端主动选择并设置策略 状态对象内部自动切换
策略/状态间关系 策略之间互不感知,平等替换 状态之间存在明确的流转关系
上下文依赖 上下文不关心具体策略,只委托调用 上下文被状态对象反向引用以完成切换
生命周期 策略通常无状态(或有注入的配置) 状态对象持有上下文引用,驱动转换
典型场景 支付方式选择、排序策略、出行方式 订单流转、电梯状态、审批流程

3.2 代码对比

策略模式------客户端主动选择:

java 复制代码
// 策略模式:客户端决定使用哪种支付策略
PaymentContext ctx = new PaymentContext();
ctx.setStrategy(new WechatPay());   // 客户端主动选择微信支付
ctx.executePay(100);
ctx.setStrategy(new AliPay());      // 客户端主动切换到支付宝
ctx.executePay(200);

状态模式------状态内部自动切换:

java 复制代码
// 状态模式:状态由对象内部自动切换,客户端只触发行为
OrderContext order = new OrderContext("ORD-001");
order.pay();    // 状态内部:待支付 → 已支付(自动)
order.ship();   // 状态内部:已支付 → 已发货(自动)
order.confirm();// 状态内部:已发货 → 已完成(自动)

3.3 选型指南

判断依据 选择策略模式 选择状态模式
切换由谁决定? 客户端主动选择 状态对象内部自动决定
存在状态流转? 策略间无流转关系 状态间有明确的转换路径
可任意替换? 是,策略可任意组合 否,受状态机规则约束
代码中大量 if-else? 用于选择算法的条件分支 用于判断当前状态的条件分支

四、总结

状态模式的核心思想是允许对象在其内部状态改变时改变它的行为,将与特定状态相关的行为封装到独立的状态类中,让状态转换由状态对象内部自动完成。

优点:

  • 消除条件判断:将大量 if-else / switch-case 分解到各个状态类中,每个类职责单一
  • 符合开闭原则:新增状态只需新增一个状态类,无需修改已有状态类和上下文
  • 状态转换显式化:状态转换逻辑集中在状态类中,流转路径一目了然
  • 提高可维护性:每个状态的逻辑独立封装,修改某个状态不影响其他状态
  • 行为随状态变化:同一操作在不同状态下有不同的响应,符合现实业务逻辑

缺点:

  • 增加类的数量:每个状态都需要一个独立的类,状态越多类越多
  • 状态类间的耦合:具体状态类需要知道下一个状态是哪个,状态间存在一定的耦合
  • 不适合状态少且固定的场景:如果只有 2-3 个简单状态,使用状态模式反而增加复杂度
  • 上下文被状态类反向引用:状态类持有上下文的引用,存在循环依赖

适用场景:

  • 对象的行为依赖于它的状态,且必须在运行时根据状态改变它的行为
  • 操作中包含大量与对象状态相关的条件判断(if-else / switch-case
  • 状态数量较多且转换逻辑复杂,需要将状态转换显式化
  • 需要通过状态机来管理对象的生命周期(如订单、审批、工作流)

状态模式 vs 策略模式 :两者结构相似但意图不同------策略模式让客户端主动选择 算法,策略间平等可互换;状态模式让对象根据内部状态自动切换 行为,状态间有明确的流转规则。简单记忆:策略模式是"我想用什么就用什么 ",状态模式是"当前状态决定我能做什么"。


参考博客:

状态模式 | 菜鸟教程:https://www.runoob.com/design-pattern/state-pattern.html

相关推荐
多加点辣也没关系1 小时前
设计模式-备忘录模式
设计模式·备忘录模式
雪度娃娃1 小时前
行为型设计模式——中介者模式
microsoft·设计模式·中介者模式
多加点辣也没关系2 小时前
设计模式-中介者模式
设计模式·中介者模式
未若君雅裁2 小时前
SpringMVC 执行流程详解
java·spring boot·spring·状态模式
geovindu19 小时前
go: Read-Write Lock Pattern
开发语言·后端·设计模式·golang·读写锁模式
前端不太难20 小时前
AI 不只是聊天框:鸿蒙 App 新入口
人工智能·状态模式·harmonyos
行走的陀螺仪1 天前
[特殊字符] JavaScript 设计模式完全指南:从入门到精通(含20种模式)
开发语言·javascript·设计模式
小陶来咯1 天前
AI Agent 设计模式:ReAct 深度解析
人工智能·react.js·设计模式
多加点辣也没关系1 天前
设计模式-责任链模式
设计模式·责任链模式