Java 23 种设计模式:从踩坑到精通 | 状态模式 —— if-else 满天飞?让状态自己决定行为

📅 2026/7/13 8:25:03 👁️ 阅读次数 📝 编程学习
Java 23 种设计模式:从踩坑到精通 | 状态模式 —— if-else 满天飞?让状态自己决定行为

Java 23 种设计模式:从踩坑到精通 | 状态模式 —— if-else 满天飞?让状态自己决定行为

摘要:当对象的行为需要根据内部状态动态改变时,大量if-elseswitch会让代码臃肿、难以扩展。状态模式将每个状态封装为独立的类,让对象在状态变化时自动切换行为,从而消除了冗长的条件判断。本文从订单状态流转的场景出发,完整讲解状态模式的原理、UML、代码实现、与策略模式的区别,并结合 Java 线程状态机、Spring State Machine、工作流引擎等应用,帮你掌握“行为随状态变化”的设计精髓。

🗺️本文阅读地图(3 分钟速览)

  • 为什么一坨if-else订单状态判断一定会炸?
  • 状态模式三角色拆解
  • 手写订单状态流转(待支付→已支付→已发货→已完成)
  • 简易交通灯:红绿灯自动切换
  • Java 线程状态机 / Spring State Machine / 工作流引擎如何体现
  • 面试必问:状态 vs 策略,到底怎么区分?

📖《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 上一篇:观察者模式 |当前:State 状态模式| 下一篇:策略模式
🔗 返回系列总目录


文章目录

  • Java 23 种设计模式:从踩坑到精通 | 状态模式 —— if-else 满天飞?让状态自己决定行为
    • 1. 从“订单状态”的一堆 if-else 说起
      • 1.1 你的场景该不该用状态模式?
    • 2. 模式定义与 UML 结构
      • 图文解析(配合上述 UML 图)
    • 3. 代码实现:订单状态流转
      • 3.1 抽象状态
      • 3.2 具体状态:待支付
      • 3.3 具体状态:已支付
      • 3.4 具体状态:已发货
      • 3.5 其他状态:已完成、已取消
      • 3.6 上下文:订单
      • 3.7 客户端
    • 4. 代码实现:简易交通灯
      • 4.1 抽象状态
      • 4.2 具体状态
      • 4.3 上下文:交通灯
      • 4.4 客户端
    • 5. 状态模式 vs 策略模式
    • 6. 优缺点一览
    • 7. 框架与实践中的应用
      • 7.1 JDK 线程状态转换
      • 7.2 Spring State Machine
      • 7.3 工作流引擎(Activiti、Camunda)
    • 8. 面试必问 + 面试官追问连环炮
    • 9. 六大设计原则在状态模式中的体现
    • 🧭 《Java 23 种设计模式:从踩坑到精通》快速导航

1. 从“订单状态”的一堆 if-else 说起

电商系统中,订单通常有多个状态:待支付、已支付、已发货、已完成、已取消。每个状态下,用户能进行的操作不同:

  • 待支付:可以支付、取消;
  • 已支付:可以发货、退款;
  • 已发货:可以确认收货;
  • 已完成:可以评价;
  • 已取消:什么也不能做。

如果不加设计,代码会变成这样:

voidhandle(Orderorder,Stringaction){if("待支付".equals(order.getStatus())){if("支付".equals(action)){// 支付逻辑order.setStatus("已支付");}elseif("取消".equals(action)){order.setStatus("已取消");}}elseif("已支付".equals(order.getStatus())){if("发货".equals(action)){...}elseif("退款".equals(action)){...}}// ...}

随着状态增多,if-else会无限膨胀,新增一个状态需要修改所有相关判断,极易出错。更麻烦的是,状态间的合法流转规则隐藏在分散的判断条件中,难以看清全貌。

状态模式(State Pattern)正是为这种“行为随状态变化”的场景而生:它将每个状态封装成一个独立的类,把与该状态相关的所有行为都放在这个类中,并在状态类中定义允许的转换,从而让对象在运行时通过改变状态来改变行为。

1.1 你的场景该不该用状态模式?

判断标准是 → 用状态模式否 → 用其他方式
对象的行为完全由内部状态决定
状态数量较多(≥3个),且可能继续增加
状态之间的转换规则复杂,需要显式管理
状态极少且固定(如只有开/关两种)简单的 if-else 或枚举即可

2. 模式定义与 UML 结构

状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。它属于行为型设计模式

图文解析(配合上述 UML 图)

状态模式的核心角色:

  • 上下文(Context:持有当前状态对象,定义客户端感兴趣的接口,并将与状态相关的请求委托给当前状态对象处理。它不知道自己处于哪个具体状态,只知道调用当前状态的handle()
  • 抽象状态(State:定义一个接口,封装与Context的一个特定状态相关的行为。每个方法代表该状态下可以执行的操作。
  • 具体状态(ConcreteStateA/ConcreteStateB:实现一个与Context某个状态相关的行为,并在需要时发起状态转换——调用context.setState(newState)切换到下一个状态。

核心机制:状态模式把“在不同状态下做不同的事”这个横切逻辑,从一长串if-else拆分到一个个独立的状态类中。上下文对象不需要知道任何状态转换逻辑,它只需把请求委派给当前状态对象。状态类自己决定何时切换、切换到哪个状态。


3. 代码实现:订单状态流转

3.1 抽象状态

publicinterfaceOrderState{voidpay(OrderContextcontext);voidcancel(OrderContextcontext);voidship(OrderContextcontext);voidconfirm(OrderContextcontext);}

💬白话:订单状态必须能响应四种操作——支付、取消、发货、确认收货。每个状态对这些操作的响应不同。

3.2 具体状态:待支付

publicclassPendingPaymentStateimplementsOrderState{@Overridepublicvoidpay(OrderContextcontext){System.out.println("支付成功,订单状态变为【已支付】");context.setState(newPaidState());}@Overridepublicvoidcancel(OrderContextcontext){System.out.println("订单已取消");context.setState(newCancelledState());}@Overridepublicvoidship(OrderContextcontext){thrownewIllegalStateException("待支付订单无法发货");}@Overridepublicvoidconfirm(OrderContextcontext){thrownewIllegalStateException("待支付订单无法确认收货");}}

💬白话:待支付状态下,只有“支付”和“取消”是合法操作。支付后自动切换到已支付状态,取消后切换到已取消状态。

3.3 具体状态:已支付

publicclassPaidStateimplementsOrderState{@Overridepublicvoidpay(OrderContextcontext){thrownewIllegalStateException("已支付订单无需重复支付");}@Overridepublicvoidcancel(OrderContextcontext){thrownewIllegalStateException("已支付订单无法取消,请申请退款");}@Overridepublicvoidship(OrderContextcontext){System.out.println("订单已发货");context.setState(newShippedState());}@Overridepublicvoidconfirm(OrderContextcontext){thrownewIllegalStateException("已支付订单未发货,无法确认收货");}}

💬白话:已支付状态下只能发货,不能支付、不能取消、不能确认收货。状态类自己充当“门卫”。

3.4 具体状态:已发货

publicclassShippedStateimplementsOrderState{@Overridepublicvoidpay(OrderContextcontext){thrownewIllegalStateException("已发货订单无需支付");}@Overridepublicvoidcancel(OrderContextcontext){thrownewIllegalStateException("已发货订单无法取消");}@Overridepublicvoidship(OrderContextcontext){thrownewIllegalStateException("已发货订单无需重复发货");}@Overridepublicvoidconfirm(OrderContextcontext){System.out.println("确认收货,订单完成");context.setState(newCompletedState());}}

3.5 其他状态:已完成、已取消

publicclassCompletedStateimplementsOrderState{@Overridepublicvoidpay(OrderContextcontext){thrownewIllegalStateException("已完成订单无需支付");}@Overridepublicvoidcancel(OrderContextcontext){thrownewIllegalStateException("已完成订单无法取消");}@Overridepublicvoidship(OrderContextcontext){thrownewIllegalStateException("已完成订单无需发货");}@Overridepublicvoidconfirm(OrderContextcontext){thrownewIllegalStateException("已完成订单无需确认");}}publicclassCancelledStateimplementsOrderState{@Overridepublicvoidpay(OrderContextcontext){thrownewIllegalStateException("已取消订单无法支付");}@Overridepublicvoidcancel(OrderContextcontext){thrownewIllegalStateException("已取消订单无法再次取消");}@Overridepublicvoidship(OrderContextcontext){thrownewIllegalStateException("已取消订单无法发货");}@Overridepublicvoidconfirm(OrderContextcontext){thrownewIllegalStateException("已取消订单无法确认");}}

💬白话:终态(已完成/已取消)的所有操作都抛异常——状态类自己就是最好的“防御性编程”。

3.6 上下文:订单

publicclassOrderContext{privateOrderStatestate;publicOrderContext(){this.state=newPendingPaymentState();// 初始状态:待支付}publicvoidsetState(OrderStatestate){this.state=state;}publicOrderStategetState(){returnstate;}// 将操作委托给当前状态publicvoidpay(){state.pay(this);}publicvoidcancel(){state.cancel(this);}publicvoidship(){state.ship(this);}publicvoidconfirm(){state.confirm(this);}}

💬白话:订单自己不判断状态,直接把请求转发给当前状态对象。状态对象处理请求后,如果需要,会调用context.setState()切换状态。

3.7 客户端

OrderContextorder=newOrderContext();order.pay();// 支付成功,订单状态变为【已支付】order.ship();// 订单已发货order.confirm();// 确认收货,订单完成order.cancel();// 抛出异常:已完成订单无法取消

所有状态转换规则被清晰封装在各个状态类中,上下文类干净得只剩委托。新增一个状态(如“退款中”)只需新增一个状态类,修改相关状态类的转换逻辑,上下文和已有状态类无需修改。


4. 代码实现:简易交通灯

另一个经典例子是交通灯,绿→黄→红循环切换。状态数量少但转换规则明确。

4.1 抽象状态

interfaceTrafficLightState{voidswitchLight(TrafficLightlight);}

4.2 具体状态

classGreenStateimplementsTrafficLightState{@OverridepublicvoidswitchLight(TrafficLightlight){System.out.println("绿灯 → 黄灯");light.setState(newYellowState());}}classYellowStateimplementsTrafficLightState{@OverridepublicvoidswitchLight(TrafficLightlight){System.out.println("黄灯 → 红灯");light.setState(newRedState());}}classRedStateimplementsTrafficLightState{@OverridepublicvoidswitchLight(TrafficLightlight){System.out.println("红灯 → 绿灯");light.setState(newGreenState());}}

💬白话:每个灯都知道自己的下一个灯是谁。绿灯→黄灯→红灯→绿灯,循环往复。

4.3 上下文:交通灯

classTrafficLight{privateTrafficLightStatestate;publicTrafficLight(){this.state=newRedState();// 初始红灯}publicvoidsetState(TrafficLightStatestate){this.state=state;}publicvoidswitchLight(){state.switchLight(this);}}

4.4 客户端

TrafficLightlight=newTrafficLight();light.switchLight();// 红灯 → 绿灯light.switchLight();// 绿灯 → 黄灯light.switchLight();// 黄灯 → 红灯

5. 状态模式 vs 策略模式

这是面试中极容易混淆的一对:

对比维度状态模式策略模式
目的根据对象内部状态自动改变行为根据不同情况选择不同算法
谁决定切换状态类自己决定何时切换到另一个状态客户端显式选择策略
关注点状态之间的流转和转换算法的替换与组合
上下文感知状态知道上下文的存在,并可能调用上下文方法策略通常不知道上下文的存在
典型应用订单状态机、线程生命周期支付方式、排序算法、折扣策略

💡简单记忆:状态模式是“被动切换”(状态自己决定下一步),策略模式是“主动选择”(客户端选择算法)。状态模式中状态类之间通常有明确的转换关系,策略模式中策略类之间完全独立。


6. 优缺点一览

优点缺点
消除庞大的条件判断:将每个状态的行为分散到独立的类中类数量增加:每个状态一个类,状态多时类数量多
符合开闭原则:新增状态只需新增一个状态类状态类之间可能产生依赖:转换逻辑需要知道目标状态类
状态转换显式化:合法流转规则集中在状态类中上下文与状态双向依赖:状态类需要引用上下文
行为可动态切换:运行时切换状态即可改变对象行为简单状态机过度设计:2-3个固定状态用枚举更简洁

7. 框架与实践中的应用

7.1 JDK 线程状态转换

Java 线程的 6 个状态(NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED)本身就是状态模式的体现。Thread.start()Object.wait()notify()等操作都会导致线程状态的自动流转。

7.2 Spring State Machine

Spring 提供了专门的状态机框架,支持定义状态、事件、转换、守卫、动作等复杂状态流转逻辑,广泛应用于订单、工作流等场景。

7.3 工作流引擎(Activiti、Camunda)

工作流引擎的核心就是状态模式,将业务流程中的每个节点定义为一个状态,通过提交、审批、驳回等操作驱动状态转换。


8. 面试必问 + 面试官追问连环炮

基础必问

  • 状态模式如何解决 if-else 问题?→ 将每个分支行为封装为独立状态类,通过多态消除条件判断。
  • 状态模式与策略模式的区别?→ 状态模式状态自己决定转换,策略模式由客户端切换策略。
  • JDK 哪里用了状态模式?→ 线程的状态转换(NEW → RUNNABLE → WAITING → ...)。

面试官追问

  • “状态模式中状态类为什么要持有上下文引用?”
    👉 以便在状态转换时调用context.setState()切换到新状态。没有上下文引用,状态就无法改变对象的行为。
  • “如果状态转换逻辑很复杂,状态类会膨胀怎么办?”
    👉 使用状态机框架(如 Spring State Machine)将状态、事件、转换规则配置化,避免手写大量状态类。
  • “状态模式和策略模式能配合使用吗?”
    👉 可以。状态决定“什么时候该做什么”,策略决定“做这件事用哪种算法”。例如订单状态决定“该结算了”,策略决定“用满减还是打折结算”。

🎉恭喜:如果你能立刻画出订单的状态流转图,并清楚状态与策略的本质区别,你已经掌握了行为型模式中“行为随状态变化”的设计精髓。


9. 六大设计原则在状态模式中的体现

设计原则在状态模式中的体现
单一职责原则(SRP)每个状态类只负责一种状态下的行为逻辑
开闭原则(OCP)新增状态无需修改上下文,只需新增状态类
里氏替换原则(LSP)所有状态类都实现State接口,可相互替换
依赖倒置原则(DIP)上下文依赖抽象State接口,不依赖具体状态类
接口隔离原则(ISP)State接口定义该状态下所有可能的操作
迪米特法则(LoD)上下文只知道当前状态,不知道状态如何实现转换

🧭 《Java 23 种设计模式:从踩坑到精通》快速导航

  • 开篇:系列介绍与目录
  • 上一篇:观察者模式 —— 发布-订阅,你每天都在用的模式
  • 当前:状态模式 —— if-else 满天飞?让状态自己决定行为(你在这里)
  • 下一篇:策略模式 —— 算法族的封装与切换,告别 if-else
  • 创建型模式汇总:单例、工厂、建造者、原型
  • 结构型模式汇总:适配器、装饰器、代理……
  • 行为型模式汇总:观察者、策略、模板方法……

🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀下一篇:策略模式:算法族的封装与切换,告别 if-else!

📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。