狀態模式:有限狀態機在電商訂單系統中的設計與實現
一、模式核心:用狀態切換驅動行為變化
在電商訂單系統中,訂單狀態會隨著用戶操作動態變化:「已創建」的訂單支付后變為「已支付」,發貨后變為「已發貨」,不同狀態下的操作權限和業務邏輯差異巨大。傳統方式通過大量if-else
判斷狀態,導致代碼臃腫且難以維護。狀態模式(State Pattern) 通過將狀態封裝為獨立類,使對象在不同狀態下自動切換行為,核心解決:
- 狀態驅動行為:不同狀態對應不同操作邏輯,避免海量條件判斷
- 狀態轉換可控:集中管理狀態遷移規則,確保狀態變化符合業務流程
核心思想與 UML 類圖
二、核心實現:構建可擴展的訂單狀態機
1. 定義狀態接口(封裝狀態相關操作)
public interface OrderState {// 支付操作:不同狀態下支付邏輯不同void pay(OrderContext context);// 發貨操作:僅特定狀態允許發貨void deliver(OrderContext context);// 取消操作:不同狀態下取消流程不同void cancel(OrderContext context);
}
2. 實現具體狀態類(封裝各狀態的行為)
已創建狀態(允許支付和取消)
public class CreatedState implements OrderState {@Overridepublic void pay(OrderContext context) {System.out.println("訂單創建狀態:執行支付流程...");context.setCurrentState(new PaidState()); // 切換到已支付狀態System.out.println("狀態變更:已創建 → 已支付");}@Overridepublic void deliver(OrderContext context) {throw new IllegalStateException("錯誤:未支付訂單不能發貨");}@Overridepublic void cancel(OrderContext context) {System.out.println("訂單創建狀態:執行取消流程(無需扣款)");context.setCurrentState(new CanceledState()); // 切換到已取消狀態}
}
已支付狀態(允許發貨和取消)
public class PaidState implements OrderState {@Overridepublic void pay(OrderContext context) {throw new IllegalStateException("錯誤:訂單已支付,請勿重復支付");}@Overridepublic void deliver(OrderContext context) {System.out.println("訂單支付狀態:執行發貨流程...");context.setCurrentState(new DeliveredState()); // 切換到已發貨狀態System.out.println("狀態變更:已支付 → 已發貨");}@Overridepublic void cancel(OrderContext context) {System.out.println("訂單支付狀態:執行取消流程(需退款)");context.setCurrentState(new CanceledState());}
}
3. 上下文類(管理狀態切換與狀態相關數據)
public class OrderContext {private OrderState currentState;private final String orderId;public OrderContext(String orderId) {this.orderId = orderId;this.currentState = new CreatedState(); // 初始狀態為已創建}// 狀態切換入口public void setCurrentState(OrderState state) {this.currentState = state;}// 對外暴露的業務操作,委托給當前狀態處理public void pay() {currentState.pay(this);}public void deliver() {currentState.deliver(this);}public void cancel() {currentState.cancel(this);}
}
4. 客戶端調用示例(狀態流轉演示)
public class ClientDemo {public static void main(String[] args) {OrderContext order = new OrderContext("ORDER_1001");// 支付操作:創建狀態 → 支付狀態order.pay(); // 輸出:支付流程 & 狀態變更// 發貨操作:支付狀態 → 發貨狀態order.deliver(); // 輸出:發貨流程 & 狀態變更// 嘗試重復支付(已支付狀態不允許)try {order.pay();} catch (IllegalStateException e) {System.out.println("異常:" + e.getMessage()); // 輸出錯誤信息}}
}
三、進階:構建健壯的狀態機框架
1. 狀態工廠(集中管理狀態實例)
public class OrderStateFactory {private static final Map<StateType, OrderState> STATE_POOL = new EnumMap<>(StateType.class);static {STATE_POOL.put(StateType.CREATED, new CreatedState());STATE_POOL.put(StateType.PAID, new PaidState());// 注冊所有狀態類}public static OrderState getState(StateType type) {return STATE_POOL.get(type);}
}// 使用枚舉定義狀態類型(避免魔法值)
enum StateType {CREATED, PAID, DELIVERED, CANCELED
}
2. 狀態轉換校驗(防止非法狀態遷移)
public abstract class BaseOrderState implements OrderState {// 定義合法的狀態轉換規則protected abstract Set<StateType> allowedNextStates();@Overridepublic final void transitionTo(OrderContext context, StateType nextState) {if (allowedNextStates().contains(nextState)) {context.setCurrentState(OrderStateFactory.getState(nextState));} else {throw new IllegalArgumentException("非法狀態轉換:當前狀態" + getCurrentState() + "不能轉換為" + nextState);}}
}// 具體狀態類實現合法轉換規則
public class CreatedState extends BaseOrderState {@Overrideprotected Set<StateType> allowedNextStates() {return Set.of(StateType.PAID, StateType.CANCELED); // 僅允許支付或取消}
}
3. 可視化狀態機(狀態流轉圖)
四、框架與源碼中的狀態模式實踐
1. Spring State Machine(專業狀態機框架)
-
核心組件:
StateMachine
:管理狀態和轉換Transition
:定義狀態轉換條件(如支付成功觸發狀態變更)
-
使用示例:
// 定義訂單狀態和事件 StateMachine<OrderState, OrderEvent> stateMachine = StateMachineBuilder.<OrderState, OrderEvent>builder().withStates().initial(OrderState.CREATED).state(OrderState.PAID).end(OrderState.CANCELED, OrderState.COMPLETED).withTransitions().from(OrderState.CREATED).to(OrderState.PAID).on(OrderEvent.PAY).build();stateMachine.sendEvent(OrderEvent.PAY); // 觸發狀態轉換
2. MyBatis 事務狀態管理
Executor
接口根據事務狀態(自動提交 / 手動提交)切換執行邏輯- 通過
BaseExecutor
的子類(如SimpleExecutor
、BatchExecutor
)實現不同狀態下的行為
3. TCP 連接狀態(Java NIO 實現)
SelectionKey
的狀態(連接、可讀、可寫)通過狀態模式管理事件分發- 避免大量
if (key.isReadable())
類型的條件判斷
五、避坑指南:正確使用狀態模式的 3 個要點
1. 避免狀態爆炸(控制狀態數量)
- ? 反模式:為每個細小狀態創建獨立類(如訂單的「支付中」「發貨中」)
- ? 最佳實踐:
- 合并相似狀態(如「待審核」「審核中」合并為「審核狀態」)
- 使用狀態工廠 + 枚舉統一管理狀態實例
2. 處理狀態轉換的原子性
- 在分布式系統中,狀態變更需結合分布式鎖或事務保證原子性
// 分布式場景下的狀態轉換(偽代碼)
public void safeTransition(OrderContext context, StateType nextState) {String lockKey = "order_state_lock:" + context.getOrderId();try (RedissonLock lock = redisson.getLock(lockKey)) {lock.lock();currentState.transitionTo(context, nextState);}
}
3. 狀態類的職責單一性
- 狀態類應專注于狀態相關行為,避免包含業務邏輯之外的代碼
- 復雜業務邏輯可提取為獨立服務(如
PaymentService
、DeliveryService
)
六、總結:何時該用狀態模式?
適用場景 | 核心特征 | 典型案例 |
---|---|---|
對象狀態驅動行為 | 不同狀態下操作邏輯差異大,且狀態可枚舉 | 訂單狀態機、電梯控制系統、工作流引擎 |
狀態轉換規則復雜 | 需要集中管理合法的狀態遷移路徑 | 游戲角色狀態(戰斗 / 待機 / 死亡)、設備狀態(開機 / 待機 / 關機) |
避免海量條件判斷 | 拒絕if-else 地獄,追求代碼可維護性 | 編譯器狀態(詞法分析 / 語法分析 / 語義分析) |
狀態模式通過「狀態封裝 + 行為委托」的設計,將狀態相關的復雜性從業務邏輯中剝離,使系統在面對狀態變化時更具彈性。下一篇我們將深入探討責任鏈模式,解析從 Sentinel 流控到審批流程的鏈式處理邏輯,敬請期待!
擴展思考:狀態模式 vs 策略模式
兩者都通過「封裝變化」實現行為切換,但核心目標不同:
模式 | 變化維度 | 狀態關聯 | 典型應用 |
---|---|---|---|
狀態模式 | 對象的狀態(動態變化) | 狀態之間存在依賴和轉換 | 狀態機驅動的業務流程 |
策略模式 | 算法或策略的選擇(靜態替換) | 無狀態依賴,策略間獨立 | 不同排序算法、支付方式選擇 |
理解這種差異,能幫助我們在設計時更精準地選擇合適的模式。