揭秘设计模式:状态设计模式 优雅地管理对象状态

告别 if-else 地狱:用状态模式优雅地管理对象状态

在软件开发中,我们经常会遇到这样的问题:一个对象的行为,会随着它自身的状态改变而改变。

想象一个常见的在线订单 系统:一个订单可以处于 新建已发货已完成已取消 等多种状态。在不同的状态下,它能执行的操作也不同:比如,只有在 新建 状态下才能发货,而一旦发货,就不能被取消。

不用状态模式,我们通常怎么做?

在没有设计模式的帮助下,处理这类问题最常见的做法,就是在核心类中用大量的 if-elseswitch 语句来判断当前状态,然后执行不同的逻辑。

我们来看一个常规的代码实现:

Java 复制代码
public class Order {
    private String status; // 订单状态,比如 "NEW", "SHIPPED"

    public Order() {
        this.status = "NEW";
    }

    public void shipOrder() {
        // 大量的 if-else 语句来处理不同状态下的发货逻辑
        if (this.status.equals("NEW")) {
            System.out.println("订单已支付,正在发货...");
            this.status = "SHIPPED";
        } else if (this.status.equals("SHIPPED")) {
            System.out.println("订单已发货,不能重复发货。");
        } else if (this.status.equals("COMPLETED")) {
            System.out.println("订单已完成,不能再发货。");
        }
    }
    
    public void cancelOrder() {
        // 又一个庞大的 if-else 语句来处理取消逻辑
        if (this.status.equals("NEW")) {
            System.out.println("订单已取消。");
            this.status = "CANCELLED";
        } else if (this.status.equals("SHIPPED")) {
            System.out.println("订单已发货,不能取消。");
        }
    }
}

这种代码最初看起来没问题,并且一开始的第一直觉就是这样来实现,但它存在严重的维护问题:

代码臃肿 :所有的状态判断和业务逻辑都集中在Order类里,导致这个类非常庞大。

违反开闭原则:每增加一个新状态或新操作,都必须回来修改这个核心类,涉及修改就要把这个类全部测一遍,这极大地增加了维护难度和引入 Bug 的风险。

难以理解:随着状态增多,代码变得难以阅读和理解,状态之间的关联关系,排斥关系会变得很难理清楚。

这时,状态模式 登场了。它最核心的思想是:把每一种状态的行为都封装到一个独立的类中,从根本上解决了上述问题。

目前来看还是很简单的逻辑,真实的业务场景更加复杂,还会涉及到状态回退,以及更为致命的流程变更。

一个完整的状态模式 Java 示例

下面,我们将所有组件整合到一个完整的 Java 代码示例中,并配上详细的说明,让你能够轻松理解每个部分。

类图

1. 状态接口(OrderState.java

这个接口是状态模式的核心,它定义了订单在不同状态下能执行的所有操作。所有具体的行为都被抽象到这个接口中。

Java 复制代码
public interface OrderState {
    void ship(Order order);
    void cancel(Order order);
    void complete(Order order);
}

2. 具体状态类

为每种状态创建一个独立的类,将特定状态下的行为逻辑和状态转换封装在各自的类中。

Java 复制代码
// 新建状态:订单可以被发货或取消
public class NewState implements OrderState {
    @Override
    public void ship(Order order) {
        System.out.println("订单已支付,正在发货...");
        // 状态转换:从NewState变为ShippedState
        order.setState(new ShippedState());
    }
    
    @Override
    public void cancel(Order order) {
        System.out.println("订单已取消。");
        // 状态转换:从NewState变为CancelledState
        order.setState(new CancelledState());
    }
    
    @Override
    public void complete(Order order) {
        System.out.println("订单在'新建'状态下无法被完成。");
    }
}

// 已发货状态:订单可以被完成,但不能被取消或重复发货
public class ShippedState implements OrderState {
    @Override
    public void ship(Order order) {
        System.out.println("订单已发货,不能重复发货。");
    }

    @Override
    public void cancel(Order order) {
        System.out.println("订单已发货,不能取消。");
    }

    @Override
    public void complete(Order order) {
        System.out.println("订单已完成。");
        // 状态转换
        order.setState(new CompletedState());
    }
}

// 已完成状态:任何操作都无效
public class CompletedState implements OrderState {
    @Override
    public void ship(Order order) {
        System.out.println("订单已完成,不能再进行任何操作。");
    }

    @Override
    public void cancel(Order order) {
        System.out.println("订单已完成,不能再进行任何操作。");
    }

    @Override
    public void complete(Order order) {
        System.out.println("订单已完成,不能重复完成。");
    }
}

// 已取消状态:任何操作都无效
public class CancelledState implements OrderState {
    @Override
    public void ship(Order order) {
        System.out.println("订单已取消,无法进行任何操作。");
    }

    @Override
    public void cancel(Order order) {
        System.out.println("订单已取消,不能重复取消。");
    }

    @Override
    public void complete(Order order) {
        System.out.println("订单已取消,无法进行任何操作。");
    }
}

3. 上下文类(Order.java

这是核心类,它不再包含复杂的 if-else 逻辑。它只负责维护一个当前状态的引用,并将操作委托给它。

Java 复制代码
import java.util.Objects;

public class Order {
    private OrderState state;

    public Order() {
        this.state = new NewState(); // 初始状态为NewState
    }

    // 设置状态的方法,供状态类内部调用进行状态转换
    public void setState(OrderState state) {
        System.out.println("状态从 " + this.state.getClass().getSimpleName() + 
                           " 转换为 " + state.getClass().getSimpleName());
        this.state = Objects.requireNonNull(state);
    }

    // 暴露给外部调用的方法,将请求委托给当前状态对象处理
    public void ship() {
        this.state.ship(this);
    }
    
    public void cancel() {
        this.state.cancel(this);
    }
    
    public void complete() {
        this.state.complete(this);
    }
}

4. 测试类(Main.java

最后,我们通过一个简单的测试类来运行整个流程,观察状态如何进行转换。

Java 复制代码
public class Main {
    public static void main(String[] args) {
        Order order = new Order();
        
        System.out.println("--- 初始状态:新建订单 ---");
        order.ship();       // 订单发货
        
        System.out.println("\n--- 状态:已发货 ---");
        order.ship();       // 再次发货,操作无效
        order.complete();   // 完成订单
        
        System.out.println("\n--- 状态:已完成 ---");
        order.cancel();     // 无法取消
    }
}

运行结果:

diff 复制代码
--- 初始状态:新建订单 ---
订单已支付,正在发货...
状态从 NewState 转换为 ShippedState

--- 状态:已发货 ---
订单已发货,不能重复发货。
订单已完成。
状态从 ShippedState 转换为 CompletedState

--- 状态:已完成 ---
订单已完成,不能再进行任何操作。

状态模式的优缺点

优点

开闭原则:增加一个新状态,只需添加一个新类,无需修改现有代码,我们只需要保证这个类是正常的,大大降低系统因为扩展导致bug的概率。

消除 if-else:用多态代替了复杂的条件判断,代码更清晰、更易读,状态逻辑关系也更好梳理。代码维护和流程变更的情况处理起来更加友好。

更好的封装:每个状态的逻辑都独立封装,降低了耦合。

缺点

类的数量增加:对于简单的状态机来说,可能会导致类太多,显得过度设计。

代码结构变复杂:对于初学者来说,这种多类协作的方式可能更难理解。

状态模式 vs. 策略模式

虽然两者结构相似,但目的和驱动力完全不同。

特性 状态模式 (State) 策略模式 (Strategy)
目的 解决对象行为随其内部状态而改变的问题。 解决可互换算法的问题,允许运行时选择不同的行为。
行为改变的驱动力 由对象内部控制。一个状态对象通常会负责把上下文(Context)切换到下一个状态。 由客户端外部控制。客户端选择并配置一个具体策略,然后执行。
场景 行为是动态、递进的,如订单的生命周期、红绿灯状态。 行为是静态、可选择的,如支付方式、排序算法。

我们可以用一个游戏角色的攻击系统来巩固一下两者区别

假设角色有 普通攻击魔法攻击弓箭攻击三种方式。这些攻击方式是由玩家 (也就是外部客户端)选择的,这种可供选择、可随时切换的行为,非常适合用策略模式来实现。每种攻击方式都是一种策略。

状态模式 则更适合处理角色在站立跳跃奔跑等不同状态下的行为。角色进入"跳跃"状态后,其行为(比如移动方式、可用技能)会由角色自身根据内部状态自动改变,而不是由外部决定。

状态模式在常用开发框架中的应用

状态模式在许多主流的开发框架中都有实际应用,只是表现形式有所不同。

  • Spring State Machine: 这是 Spring 框架中一个专门用于实现状态机模式的子项目。它提供了丰富的注解和配置,让开发者可以非常便捷地定义状态、事件和转换,非常适合复杂的业务流程(如订单处理、审批流程)。
  • 工作流/BPM 引擎: 许多业务流程管理(BPM)引擎(如 Activiti, Camunda)的核心就是状态机。一个流程实例的生命周期,就是从一个状态转换到另一个状态,每个状态都有特定的处理逻辑。
相关推荐
清风徐来QCQ3 小时前
关于maven编译没把resources资源包含进target目录
java·开发语言·maven
失散133 小时前
分布式专题——18 Zookeeper选举Leader源码剖析
java·分布式·zookeeper·云原生·架构
该用户已不存在3 小时前
盘点9个Python的库
后端·python
RoyLin3 小时前
图数据库基础
前端·后端·typescript
IT_陈寒3 小时前
Vite 5大性能优化技巧:构建速度提升300%的实战分享!
前端·人工智能·后端
我是谁的程序员3 小时前
iOS 26 帧率测试实战指南,Liquid Glass 动画性能、滚动滑动帧率对比、旧机型流畅性与 uni-app 优化策略
后端
LucianaiB3 小时前
手把手教你用 Trickle + 豆包·Seedream-4.0 API 打造专属拍照姿势生成器
后端
华仔啊3 小时前
搞定 Java 高并发秒杀,掌握这 7 个核心设计原则就够了
java·后端
AAA修煤气灶刘哥3 小时前
对象存储封神指南:OSS 分片上传 + 重复校验 + 防毒,代码直接抄!
java·后端·面试