直播镜头免安装绿色版
7.24G · 2025-10-27
if-else 地狱:用状态模式优雅地管理对象状态在软件开发中,我们经常会遇到这样的问题:一个对象的行为,会随着它自身的状态改变而改变。
想象一个常见的在线订单系统:一个订单可以处于 新建、已发货、已完成、已取消 等多种状态。在不同的状态下,它能执行的操作也不同:比如,只有在 新建 状态下才能发货,而一旦发货,就不能被取消。
在没有设计模式的帮助下,处理这类问题最常见的做法,就是在核心类中用大量的 if-else 或 switch 语句来判断当前状态,然后执行不同的逻辑。
我们来看一个常规的代码实现:
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 代码示例中,并配上详细的说明,让你能够轻松理解每个部分。
OrderState.java)这个接口是状态模式的核心,它定义了订单在不同状态下能执行的所有操作。所有具体的行为都被抽象到这个接口中。
public interface OrderState {
void ship(Order order);
void cancel(Order order);
void complete(Order order);
}
为每种状态创建一个独立的类,将特定状态下的行为逻辑和状态转换封装在各自的类中。
// 新建状态:订单可以被发货或取消
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("订单已取消,无法进行任何操作。");
}
}
Order.java)这是核心类,它不再包含复杂的 if-else 逻辑。它只负责维护一个当前状态的引用,并将操作委托给它。
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);
}
}
Main.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(); // 无法取消
}
}
运行结果:
--- 初始状态:新建订单 ---
订单已支付,正在发货...
状态从 NewState 转换为 ShippedState
--- 状态:已发货 ---
订单已发货,不能重复发货。
订单已完成。
状态从 ShippedState 转换为 CompletedState
--- 状态:已完成 ---
订单已完成,不能再进行任何操作。
优点:
开闭原则:增加一个新状态,只需添加一个新类,无需修改现有代码,我们只需要保证这个类是正常的,大大降低系统因为扩展导致bug的概率。
消除 if-else:用多态代替了复杂的条件判断,代码更清晰、更易读,状态逻辑关系也更好梳理。代码维护和流程变更的情况处理起来更加友好。
更好的封装:每个状态的逻辑都独立封装,降低了耦合。
缺点:
类的数量增加:对于简单的状态机来说,可能会导致类太多,显得过度设计。
代码结构变复杂:对于初学者来说,这种多类协作的方式可能更难理解。
虽然两者结构相似,但目的和驱动力完全不同。
| 特性 | 状态模式 (State) | 策略模式 (Strategy) |
|---|---|---|
| 目的 | 解决对象行为随其内部状态而改变的问题。 | 解决可互换算法的问题,允许运行时选择不同的行为。 |
| 行为改变的驱动力 | 由对象内部控制。一个状态对象通常会负责把上下文(Context)切换到下一个状态。 | 由客户端外部控制。客户端选择并配置一个具体策略,然后执行。 |
| 场景 | 行为是动态、递进的,如订单的生命周期、红绿灯状态。 | 行为是静态、可选择的,如支付方式、排序算法。 |
我们可以用一个游戏角色的攻击系统来巩固一下两者区别
假设角色有 普通攻击、魔法攻击和弓箭攻击三种方式。这些攻击方式是由玩家(也就是外部客户端)选择的,这种可供选择、可随时切换的行为,非常适合用策略模式来实现。每种攻击方式都是一种策略。
而状态模式则更适合处理角色在站立、跳跃、奔跑等不同状态下的行为。角色进入"跳跃"状态后,其行为(比如移动方式、可用技能)会由角色自身根据内部状态自动改变,而不是由外部决定。
状态模式在许多主流的开发框架中都有实际应用,只是表现形式有所不同。