詳解Java中三種狀態(tài)機(jī)實(shí)現(xiàn)方式來優(yōu)雅消滅 if-else 嵌套
1. 前言
在博主之前 【設(shè)計模式】的專欄中,介紹過一個狀態(tài)模式(津津樂道設(shè)計模式 - 狀態(tài)模式詳解),并以交通信號燈的實(shí)現(xiàn)進(jìn)行狀態(tài)模式的演示講解,實(shí)際上狀態(tài)模式也特別適合處理那些包含大量條件語句(如if-else或switch)的復(fù)雜狀態(tài)轉(zhuǎn)換邏輯。
為什么使用狀態(tài)模式消除if-else?
在日常 Java 開發(fā)中,我們常常遇到這樣的情況:
- 不同的狀態(tài)有不同的處理邏輯
- 狀態(tài)之間會相互切換
- 不同狀態(tài)下執(zhí)行的動作可能完全不同
這時,如果我們用 if-else 或 switch-case 來寫,代碼很容易變得臃腫難維護(hù):比如下面代碼
if (status == 0) {
// 待支付邏輯
} else if (status == 1) {
// 已支付邏輯
} else if (status == 2) {
// 已發(fā)貨邏輯
} else if (status == 3) {
// 已收貨邏輯
}
這種寫法的問題:
邏輯分支多,擴(kuò)展性差
新增狀態(tài)時,需要修改原有代碼(違反 開閉原則)
狀態(tài)切換邏輯容易分散在各個 if-else 中,不夠集中
那么使用狀態(tài)機(jī)(State Machine)也可稱狀態(tài)模式,就可以解決上述這些問題,并具備以下優(yōu)點(diǎn):
- 消除復(fù)雜的條件判斷語句
- 提高代碼可讀性和可維護(hù)性
- 使?fàn)顟B(tài)轉(zhuǎn)換邏輯更加清晰
- 符合開閉原則,易于擴(kuò)展新狀態(tài)
2. 復(fù)現(xiàn)傳統(tǒng)if-else實(shí)現(xiàn)的業(yè)務(wù)場景問題
假設(shè)有一個電商訂單系統(tǒng),訂單狀態(tài)有:
- 待支付(PendingPay)
- 已支付(Paid)
- 已發(fā)貨(Shipped)
- 已完成(Completed)
訂單可以:
- 從待支付 → 支付 → 發(fā)貨 → 完成
- 取消訂單(在待支付狀態(tài)下)
那么可能你的代碼就是像這樣的:
public class OrderServiceIfElse {
public void handle(int status, String action) {
if (status == 0) {
if ("pay".equals(action)) {
System.out.println("訂單已支付");
} else if ("cancel".equals(action)) {
System.out.println("訂單已取消");
}
} else if (status == 1) {
if ("ship".equals(action)) {
System.out.println("訂單已發(fā)貨");
}
} else if (status == 2) {
if ("confirm".equals(action)) {
System.out.println("訂單已完成");
}
}
}
}
又或者是這樣的:
public class Order {
private String state;
public void process() {
if ("NEW".equals(state)) {
System.out.println("處理新訂單");
state = "PROCESSING";
} else if ("PROCESSING".equals(state)) {
System.out.println("訂單處理中");
state = "SHIPPED";
} else if ("SHIPPED".equals(state)) {
System.out.println("訂單已發(fā)貨");
state = "DELIVERED";
} else if ("DELIVERED".equals(state)) {
System.out.println("訂單已完成");
} else {
throw new IllegalStateException("無效訂單狀態(tài): " + state);
}
}
}
無論是哪一種寫法,都會存在以下問題:
當(dāng)狀態(tài)增多時,代碼變得臃腫難以維護(hù)
違反開閉原則,添加新狀態(tài)需要修改現(xiàn)有代碼
狀態(tài)轉(zhuǎn)換邏輯分散在多個地方
難以跟蹤狀態(tài)轉(zhuǎn)換規(guī)則
3. 用狀態(tài)機(jī)模式改造
下面我們使用狀態(tài)模式重構(gòu)上述訂單處理流程:
3.1 定義狀態(tài)接口
public interface OrderState {
void pay(OrderContext context);
void ship(OrderContext context);
void confirm(OrderContext context);
void cancel(OrderContext context);
}
3.2 創(chuàng)建上下文類
public class OrderContext {
private OrderState state;
public OrderContext(OrderState state) {
this.state = state;
}
public void setState(OrderState state) {
this.state = state;
}
public void pay() {
state.pay(this);
}
public void ship() {
state.ship(this);
}
public void confirm() {
state.confirm(this);
}
public void cancel() {
state.cancel(this);
}
}
3.3 定義具體狀態(tài)類
// 待支付
public class PendingPayState 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("訂單已取消");
}
}
// 已支付
public class PaidState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("訂單已支付,請勿重復(fù)支付");
}
@Override
public void ship(OrderContext context) {
System.out.println("訂單已發(fā)貨");
context.setState(new ShippedState());
}
@Override
public void confirm(OrderContext context) {
System.out.println("訂單還未發(fā)貨");
}
@Override
public void cancel(OrderContext context) {
System.out.println("已支付訂單不能取消");
}
}
// 已發(fā)貨
public class ShippedState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("訂單已支付");
}
@Override
public void ship(OrderContext context) {
System.out.println("訂單已經(jīng)發(fā)貨");
}
@Override
public void confirm(OrderContext context) {
System.out.println("訂單已完成");
context.setState(new CompletedState());
}
@Override
public void cancel(OrderContext context) {
System.out.println("發(fā)貨后不能取消訂單");
}
}
// 已完成
public class CompletedState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("訂單已完成,無法支付");
}
@Override
public void ship(OrderContext context) {
System.out.println("訂單已完成,無法發(fā)貨");
}
@Override
public void confirm(OrderContext context) {
System.out.println("訂單已完成");
}
@Override
public void cancel(OrderContext context) {
System.out.println("訂單已完成,無法取消");
}
}
3.4 測試使用
public class StateMachineTest {
public static void main(String[] args) {
// 初始狀態(tài):待支付
OrderContext order = new OrderContext(new PendingPayState());
order.pay(); // 支付
order.ship(); // 發(fā)貨
order.confirm(); // 確認(rèn)收貨
//分別輸出
//訂單支付成功
//訂單已發(fā)貨
//訂單已完成
}
}
4. 枚舉 + Map的輕量狀態(tài)機(jī)實(shí)現(xiàn)
這里博主再升級一下使用枚舉 + Map 的輕量狀態(tài)機(jī)實(shí)現(xiàn),這種寫法相比經(jīng)典狀態(tài)模式,代碼量更少,適合狀態(tài)數(shù)不多、且狀態(tài)邏輯相對簡單的場景
這種寫法的核心思想是:
用枚舉類定義所有狀態(tài)
每個狀態(tài)實(shí)現(xiàn)自己的行為邏輯
通過 Map 注冊狀態(tài)與處理邏輯的映射關(guān)系
直接通過當(dāng)前狀態(tài)對象來執(zhí)行對應(yīng)方法,避免 if-else
4.1 定義狀態(tài)枚舉
public enum OrderStateEnum {
PENDING_PAY {
@Override
public OrderStateEnum pay() {
System.out.println("訂單支付成功");
return PAID;
}
@Override
public OrderStateEnum ship() {
System.out.println("請先支付訂單");
return this;
}
@Override
public OrderStateEnum confirm() {
System.out.println("請先支付訂單");
return this;
}
@Override
public OrderStateEnum cancel() {
System.out.println("訂單已取消");
return this;
}
},
PAID {
@Override
public OrderStateEnum pay() {
System.out.println("訂單已支付,請勿重復(fù)支付");
return this;
}
@Override
public OrderStateEnum ship() {
System.out.println("訂單已發(fā)貨");
return SHIPPED;
}
@Override
public OrderStateEnum confirm() {
System.out.println("訂單還未發(fā)貨");
return this;
}
@Override
public OrderStateEnum cancel() {
System.out.println("已支付訂單不能取消");
return this;
}
},
SHIPPED {
@Override
public OrderStateEnum pay() {
System.out.println("訂單已支付");
return this;
}
@Override
public OrderStateEnum ship() {
System.out.println("訂單已經(jīng)發(fā)貨");
return this;
}
@Override
public OrderStateEnum confirm() {
System.out.println("訂單已完成");
return COMPLETED;
}
@Override
public OrderStateEnum cancel() {
System.out.println("發(fā)貨后不能取消訂單");
return this;
}
},
COMPLETED {
@Override
public OrderStateEnum pay() {
System.out.println("訂單已完成,無法支付");
return this;
}
@Override
public OrderStateEnum ship() {
System.out.println("訂單已完成,無法發(fā)貨");
return this;
}
@Override
public OrderStateEnum confirm() {
System.out.println("訂單已完成");
return this;
}
@Override
public OrderStateEnum cancel() {
System.out.println("訂單已完成,無法取消");
return this;
}
};
public abstract OrderStateEnum pay();
public abstract OrderStateEnum ship();
public abstract OrderStateEnum confirm();
public abstract OrderStateEnum cancel();
}
這里的設(shè)計是:
每個狀態(tài)用一個枚舉常量表示
每個枚舉常量實(shí)現(xiàn)自己的狀態(tài)切換邏輯
方法返回新的狀態(tài)枚舉,實(shí)現(xiàn)狀態(tài)流轉(zhuǎn)
4.2 配置上下文類
public class OrderContextEnum {
private OrderStateEnum state;
public OrderContextEnum(OrderStateEnum state) {
this.state = state;
}
public void pay() {
state = state.pay();
}
public void ship() {
state = state.ship();
}
public void confirm() {
state = state.confirm();
}
public void cancel() {
state = state.cancel();
}
}
4.3 測試使用
public class EnumStateMachineTest {
public static void main(String[] args) {
OrderContextEnum order = new OrderContextEnum(OrderStateEnum.PENDING_PAY);
order.pay(); // 支付
order.ship(); // 發(fā)貨
order.confirm(); // 確認(rèn)收貨
}
}
5. 使用 Spring StateMachine 實(shí)現(xiàn)訂單狀態(tài)機(jī)
Spring StateMachine 是 Spring 官方提供的狀態(tài)機(jī)框架,支持可配置狀態(tài)流轉(zhuǎn)、事件驅(qū)動、監(jiān)聽器回調(diào)等功能,特別適合業(yè)務(wù)流程復(fù)雜、狀態(tài)多變的場景。
5.1 依賴引入
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>3.2.1</version>
</dependency>
5.2 定義狀態(tài)與事件枚舉
// 訂單狀態(tài)
public enum OrderState {
PENDING_PAY, // 待支付
PAID, // 已支付
SHIPPED, // 已發(fā)貨
COMPLETED // 已完成
}
// 觸發(fā)事件
public enum OrderEvent {
PAY, // 支付
SHIP, // 發(fā)貨
CONFIRM, // 確認(rèn)收貨
CANCEL // 取消訂單
}
5.3 配置狀態(tài)機(jī)
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import java.util.EnumSet;
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
states
.withStates()
.initial(OrderState.PENDING_PAY) // 初始狀態(tài)
.states(EnumSet.allOf(OrderState.class)); // 所有狀態(tài)
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
transitions
.withExternal()
.source(OrderState.PENDING_PAY).target(OrderState.PAID).event(OrderEvent.PAY)
.and()
.withExternal()
.source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP)
.and()
.withExternal()
.source(OrderState.SHIPPED).target(OrderState.COMPLETED).event(OrderEvent.CONFIRM)
.and()
.withExternal()
.source(OrderState.PENDING_PAY).target(OrderState.PENDING_PAY).event(OrderEvent.CANCEL);
}
@Override
public void configure(StateMachineConfigurationConfigurer<OrderState, OrderEvent> config) throws Exception {
config
.withConfiguration()
.listener(new StateMachineListenerAdapter<>() {
@Override
public void stateChanged(State<OrderState, OrderEvent> from, State<OrderState, OrderEvent> to) {
System.out.println("狀態(tài)切換: "
+ (from == null ? "無" : from.getId())
+ " -> " + to.getId());
}
});
}
}
5.4 測試代碼
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;
@Component
public class OrderStateMachineTest implements CommandLineRunner {
@Autowired
private StateMachine<OrderState, OrderEvent> stateMachine;
@Override
public void run(String... args) throws Exception {
stateMachine.start();
stateMachine.sendEvent(OrderEvent.PAY);
stateMachine.sendEvent(OrderEvent.SHIP);
stateMachine.sendEvent(OrderEvent.CONFIRM);
stateMachine.stop();
}
}
觀察控制臺會輸出如下:
狀態(tài)切換: 無 -> PENDING_PAY
狀態(tài)切換: PENDING_PAY -> PAID
狀態(tài)切換: PAID -> SHIPPED
狀態(tài)切換: SHIPPED -> COMPLETED
6. 對比三種方案
| 實(shí)現(xiàn)方式 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場景 |
|---|---|---|---|
| 經(jīng)典狀態(tài)模式 | 結(jié)構(gòu)清晰,面向?qū)ο?/td> | 類文件多,狀態(tài)多時管理復(fù)雜 | 狀態(tài)多、邏輯復(fù)雜的 OO 場景 |
| 枚舉狀態(tài)機(jī) | 簡潔,集中管理 | 狀態(tài)多時枚舉類太長 | 狀態(tài)少、邏輯簡單 |
| Spring StateMachine | 功能強(qiáng)大,可配置化 | 需要額外依賴,學(xué)習(xí)成本高 | 大型系統(tǒng)、狀態(tài)規(guī)則經(jīng)常變動 |
經(jīng)典狀態(tài)模式適合復(fù)雜業(yè)務(wù)流程,易于模塊化管理
枚舉 + Map 注冊或枚舉直接實(shí)現(xiàn)行為,適合小型項(xiàng)目或簡單狀態(tài)流轉(zhuǎn)
在 Java 中,枚舉天生是單例的,用它實(shí)現(xiàn)狀態(tài)機(jī)既簡潔又線程安全
7. 總結(jié)
本文博主詳細(xì)介紹了使用狀態(tài)模式消除if-else, 通過經(jīng)典狀態(tài)模式、枚舉狀態(tài)機(jī)、Spring StateMachine 三種方式 ,從純手寫模式 → 枚舉模式 → 框架模式的完整對比,進(jìn)行了相關(guān)代碼演示。當(dāng)小伙伴們發(fā)現(xiàn)自己在編寫大量條件語句來處理對象狀態(tài)時,考慮使用狀態(tài)模式重構(gòu)您的代碼。
到此這篇關(guān)于詳解Java中三種狀態(tài)機(jī)實(shí)現(xiàn)方式來優(yōu)雅消滅 if-else 嵌套的文章就介紹到這了,更多相關(guān)Java狀態(tài)機(jī)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解析Java中的隊(duì)列和用LinkedList集合模擬隊(duì)列的方法
這篇文章主要介紹了解析Java中的隊(duì)列和用LinkedList集合模擬隊(duì)列的方法,相關(guān)算法的實(shí)現(xiàn)也是ACM中的常見題目,需要的朋友可以參考下2015-08-08
使用springboot跳轉(zhuǎn)到指定頁面和(重定向,請求轉(zhuǎn)發(fā)的實(shí)例)
這篇文章主要介紹了使用springboot跳轉(zhuǎn)到指定頁面和(重定向,請求轉(zhuǎn)發(fā)的實(shí)例),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
IDEA導(dǎo)入下載的項(xiàng)目后左側(cè)沒有樹狀層級目錄的解決方案
這篇文章主要介紹了IDEA導(dǎo)入下載的項(xiàng)目后左側(cè)沒有樹狀層級目錄的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-08-08
Java上傳文件錯誤java.lang.NoSuchMethodException的解決辦法
今天小編就為大家分享一篇關(guān)于Java上傳文件錯誤java.lang.NoSuchMethodException的解決辦法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01
Centos 7 安裝 OpenJDK 11 兩種方式及問題小結(jié)
這篇文章主要介紹了Centos 7 安裝 OpenJDK 11 兩種方式,第一種方式使用yum安裝,第二種方式使用tar解壓安裝,每種方法給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09
SpringCloud-Spring?Boot?Starter使用測試及問題小結(jié)
Spring?Boot?Starter?是在?SpringBoot?組件中被提出來的一種概念、簡化了很多煩瑣的配置、通過引入各種?Spring?Boot?Starter?包可以快速搭建出一個項(xiàng)目的腳手架,這篇文章主要介紹了SpringCloud-Spring?Boot?Starter使用測試,需要的朋友可以參考下2022-07-07
Spring?MVC全局異常處理實(shí)戰(zhàn)詳細(xì)指南
在Spring MVC框架中統(tǒng)一異常處理是一項(xiàng)重要的功能,它能夠幫助開發(fā)者集中管理并標(biāo)準(zhǔn)化系統(tǒng)中的異常處理流程,提高代碼的可讀性和可維護(hù)性,這篇文章主要介紹了Spring?MVC全局異常處理實(shí)戰(zhàn)的相關(guān)資料,需要的朋友可以參考下2026-02-02

