From cbd4a33eaf33bfa1a3bdaf421ebd8007ab63b665 Mon Sep 17 00:00:00 2001 From: meowrain Date: Sat, 14 Feb 2026 00:01:04 +0800 Subject: [PATCH] all --- .claude/settings.local.json | 8 + README.md | 323 ++++++++++++++++++ out/com/example/fsm/OrderFSMDemo.class | Bin 0 -> 7599 bytes out/com/example/fsm/core/Action.class | Bin 0 -> 310 bytes out/com/example/fsm/core/Guard.class | Bin 0 -> 309 bytes .../fsm/core/StateMachine$Builder.class | Bin 0 -> 3157 bytes .../StateMachine$StateChangeListener.class | Bin 0 -> 563 bytes out/com/example/fsm/core/StateMachine.class | Bin 0 -> 4501 bytes out/com/example/fsm/core/Transition.class | Bin 0 -> 2225 bytes out/com/example/fsm/order/OrderContext.class | Bin 0 -> 2284 bytes out/com/example/fsm/order/OrderEvent.class | Bin 0 -> 2790 bytes out/com/example/fsm/order/OrderState.class | Bin 0 -> 2768 bytes .../fsm/order/OrderStateMachineFactory.class | Bin 0 -> 5107 bytes run.bat | 25 ++ src/com/example/fsm/OrderFSMDemo.java | 214 ++++++++++++ src/com/example/fsm/core/Action.java | 20 ++ src/com/example/fsm/core/Guard.java | 25 ++ src/com/example/fsm/core/StateMachine.java | 223 ++++++++++++ src/com/example/fsm/core/Transition.java | 74 ++++ src/com/example/fsm/order/OrderContext.java | 103 ++++++ src/com/example/fsm/order/OrderEvent.java | 48 +++ src/com/example/fsm/order/OrderState.java | 61 ++++ .../fsm/order/OrderStateMachineFactory.java | 141 ++++++++ 23 files changed, 1265 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 README.md create mode 100644 out/com/example/fsm/OrderFSMDemo.class create mode 100644 out/com/example/fsm/core/Action.class create mode 100644 out/com/example/fsm/core/Guard.class create mode 100644 out/com/example/fsm/core/StateMachine$Builder.class create mode 100644 out/com/example/fsm/core/StateMachine$StateChangeListener.class create mode 100644 out/com/example/fsm/core/StateMachine.class create mode 100644 out/com/example/fsm/core/Transition.class create mode 100644 out/com/example/fsm/order/OrderContext.class create mode 100644 out/com/example/fsm/order/OrderEvent.class create mode 100644 out/com/example/fsm/order/OrderState.class create mode 100644 out/com/example/fsm/order/OrderStateMachineFactory.class create mode 100644 run.bat create mode 100644 src/com/example/fsm/OrderFSMDemo.java create mode 100644 src/com/example/fsm/core/Action.java create mode 100644 src/com/example/fsm/core/Guard.java create mode 100644 src/com/example/fsm/core/StateMachine.java create mode 100644 src/com/example/fsm/core/Transition.java create mode 100644 src/com/example/fsm/order/OrderContext.java create mode 100644 src/com/example/fsm/order/OrderEvent.java create mode 100644 src/com/example/fsm/order/OrderState.java create mode 100644 src/com/example/fsm/order/OrderStateMachineFactory.java diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..ad8adf8 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(javac:*)", + "Bash(java:*)" + ] + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..21bbd22 --- /dev/null +++ b/README.md @@ -0,0 +1,323 @@ +# 电商订单状态机 (Order FSM Demo) + +## 什么是状态机? + +**状态机**(Finite State Machine, FSM)是一种数学计算模型。它在任意时刻只能处于**一个状态**,通过接收**事件**触发**状态转换**,从一个状态跳到另一个状态。 + +用人话说就是:**状态机 = 当前状态 + 事件 → 新状态** + +### 核心概念 + +| 概念 | 英文 | 说明 | 本项目中的例子 | +|------|------|------|----------------| +| **状态 (State)** | State | 事物当前所处的情况 | 待支付、已支付、已发货... | +| **事件 (Event)** | Event | 触发状态变化的信号 | 支付、发货、确认收货... | +| **转换 (Transition)** | Transition | 从一个状态到另一个状态的跳转规则 | 待支付 --[支付]--> 已支付 | +| **守卫 (Guard)** | Guard | 转换前的条件检查,不满足则拒绝转换 | 余额是否充足? | +| **动作 (Action)** | Action | 转换发生时执行的业务逻辑 | 扣减余额、发送通知... | +| **上下文 (Context)** | Context | 承载业务数据的对象 | 订单信息(金额、商品等) | + +### 一图胜千言 + +``` + ┌──────────┐ + ┌───→│ 已取消 │ + 取消/超时 │ └──────────┘ + │ + ┌──────────┐ 支付 ┌──────────┐ 发货 ┌──────────┐ 确认收货 ┌──────────┐ 完成 ┌──────────┐ + │ 待支付 │──────→│ 已支付 │──────→│ 已发货 │────────→│ 已收货 │──────→│ 已完成 │ + └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ + │ │ + 申请退款│ 申请退款│ + ▼ ▼ + ┌──────────┐ + │ 退款中 │ + └──────────┘ + │ │ + 退款通过│ 退款拒绝│ + ▼ ▼ + ┌──────────┐ 恢复为已支付 + │ 已退款 │ + └──────────┘ +``` + +--- + +## 项目结构 + +``` +fsm_demo/ +├── src/ +│ └── com/example/fsm/ +│ ├── core/ # 状态机核心引擎(通用,可复用) +│ │ ├── Action.java # 动作接口 +│ │ ├── Guard.java # 守卫条件接口 +│ │ ├── Transition.java # 转换规则定义 +│ │ └── StateMachine.java # 状态机引擎 + Builder +│ │ +│ ├── order/ # 订单业务实现 +│ │ ├── OrderState.java # 订单状态枚举(8种状态) +│ │ ├── OrderEvent.java # 订单事件枚举(9种事件) +│ │ ├── OrderContext.java # 订单上下文(业务数据) +│ │ └── OrderStateMachineFactory.java # 订单状态机工厂(组装规则) +│ │ +│ └── OrderFSMDemo.java # 演示程序入口(5个场景) +│ +├── run.bat # Windows 一键运行脚本 +└── README.md # 本文档 +``` + +### 核心引擎 vs 业务实现 + +本项目刻意将代码分成两层: + +``` +┌─────────────────────────────┐ +│ order/ (业务层) │ ← 定义"订单有哪些状态/事件/规则" +│ OrderState, OrderEvent, │ +│ OrderContext, │ +│ OrderStateMachineFactory │ +├─────────────────────────────┤ +│ core/ (引擎层) │ ← 提供"状态机怎么运转" +│ StateMachine, Transition, │ +│ Action, Guard │ +└─────────────────────────────┘ +``` + +**引擎层**完全不知道"订单"的存在,它只认识泛型 ``。这意味着你可以用同一个引擎去做物流状态机、审批流、游戏AI等任何状态驱动的场景。 + +--- + +## 快速运行 + +### 方式一:使用脚本(推荐) + +```bash +# Windows +run.bat +``` + +### 方式二:手动编译运行 + +```bash +# 编译 +javac -encoding UTF-8 -d out src/com/example/fsm/core/*.java src/com/example/fsm/order/*.java src/com/example/fsm/OrderFSMDemo.java + +# 运行 +java -Dfile.encoding=UTF-8 -cp out com.example.fsm.OrderFSMDemo +``` + +--- + +## 演示场景 + +程序会依次运行 5 个场景,覆盖电商订单的主要流程: + +### 场景一:正常完整流程 (Happy Path) + +``` +待支付 → 已支付 → 已发货 → 已收货 → 已完成 +``` + +这是最常见的订单生命周期。买家下单、付款、卖家发货、买家收货、完成评价。 + +### 场景二:支付超时自动取消 + +``` +待支付 → 已取消 +``` + +系统检测到订单超过 30 分钟未支付,自动触发 `TIMEOUT` 事件取消订单。 + +### 场景三:支付后申请退款 + +``` +待支付 → 已支付 → 退款中 → 已退款 +``` + +买家付款后反悔,申请退款。卖家审核通过后,系统自动退还金额。 + +### 场景四:余额不足 (Guard 守卫演示) + +``` +待支付 → (支付失败,状态不变) → 待支付 +``` + +用户余额 50 元,商品 9999 元。Guard 守卫条件检测到余额不足,**拒绝状态转换**。状态保持不变。 + +### 场景五:非法操作 (状态防护演示) + +``` +待支付 → (尝试发货,被拒绝) → 待支付 +``` + +在未支付状态下直接尝试发货。状态机找不到匹配的转换规则,**抛出异常**阻止非法操作。 + +--- + +## 代码详解 + +### 1. 状态定义 (`OrderState.java`) + +```java +public enum OrderState { + WAIT_PAY("待支付", "订单已创建,等待买家付款"), + PAID("已支付", "买家已完成支付,等待卖家发货"), + SHIPPED("已发货", "卖家已发货,等待买家确认收货"), + // ... +} +``` + +用枚举定义状态是最佳实践: +- 类型安全,不可能出现拼写错误 +- IDE 自动补全 +- 可以附加元数据(中文名、描述等) + +### 2. 事件定义 (`OrderEvent.java`) + +```java +public enum OrderEvent { + PAY("支付", "买家完成付款"), + SHIP("发货", "卖家确认发货"), + CANCEL("取消订单", "买家主动取消未支付的订单"), + // ... +} +``` + +### 3. 转换规则 (`Transition.java`) + +```java +// 一条转换 = 源状态 + 事件 + 目标状态 + (可选)守卫 + (可选)动作 +public class Transition { + private final S source; // 从哪个状态 + private final E event; // 收到什么事件 + private final S target; // 跳到哪个状态 + private final Guard guard; // 跳之前检查什么条件 + private final Action action; // 跳的时候做什么事 +} +``` + +### 4. 状态机引擎 (`StateMachine.java`) + +核心的 `fireEvent` 方法: + +```java +public void fireEvent(E event, C context) { + // 1. 根据 "当前状态 + 事件" 查找转换规则 + Transition transition = transitionTable.get(currentState).get(event); + + // 2. 检查守卫条件 + if (transition.getGuard() != null && !transition.getGuard().evaluate(context)) { + throw new IllegalStateException("守卫条件不满足"); + } + + // 3. 执行动作 + if (transition.getAction() != null) { + transition.getAction().execute(context); + } + + // 4. 切换状态 + currentState = transition.getTarget(); +} +``` + +### 5. 工厂组装 (`OrderStateMachineFactory.java`) + +用 Builder 模式流畅地注册所有转换规则: + +```java +StateMachine.builder() + .initialState(OrderState.WAIT_PAY) + // 待支付 --[支付]--> 已支付(带守卫和动作) + .addTransition( + OrderState.WAIT_PAY, OrderEvent.PAY, OrderState.PAID, + ctx -> ctx.isBalanceSufficient(), // 守卫:余额充足? + ctx -> ctx.deductBalance() // 动作:扣款 + ) + // 已支付 --[发货]--> 已发货 + .addTransition(OrderState.PAID, OrderEvent.SHIP, OrderState.SHIPPED) + // ...更多规则 + .build(); +``` + +--- + +## 转换规则一览表 + +| 源状态 | 事件 | 目标状态 | 守卫条件 | 动作 | +|--------|------|----------|----------|------| +| 待支付 | 支付 | 已支付 | 余额充足 | 扣减余额 | +| 待支付 | 取消订单 | 已取消 | 无 | 无 | +| 待支付 | 支付超时 | 已取消 | 无 | 无 | +| 已支付 | 发货 | 已发货 | 无 | 记录发货时间 | +| 已支付 | 申请退款 | 退款中 | 无 | 提交退款申请 | +| 已发货 | 确认收货 | 已收货 | 无 | 无 | +| 已发货 | 申请退款 | 退款中 | 无 | 提交退款申请 | +| 已收货 | 完成 | 已完成 | 无 | 无 | +| 退款中 | 退款通过 | 已退款 | 无 | 退还余额 | +| 退款中 | 退款拒绝 | 已支付 | 无 | 无 | + +--- + +## 状态机的优势(为什么不用 if-else?) + +### 传统写法(if-else 地狱) + +```java +// 不用状态机的写法 —— 随着状态增多,代码将迅速失控 +public void handleEvent(String event) { + if (state.equals("WAIT_PAY")) { + if (event.equals("PAY")) { + if (balance >= amount) { + state = "PAID"; + deductBalance(); + } + } else if (event.equals("CANCEL")) { + state = "CANCELLED"; + } else if (event.equals("TIMEOUT")) { + state = "CANCELLED"; + } + } else if (state.equals("PAID")) { + if (event.equals("SHIP")) { + state = "SHIPPED"; + } else if (event.equals("REQUEST_REFUND")) { + state = "REFUNDING"; + } + } + // ... 每增加一个状态,嵌套就多一层 +} +``` + +### 状态机写法 + +```java +// 声明式,一目了然 +.addTransition(WAIT_PAY, PAY, PAID, 余额守卫, 扣款动作) +.addTransition(WAIT_PAY, CANCEL, CANCELLED) +.addTransition(WAIT_PAY, TIMEOUT, CANCELLED) +.addTransition(PAID, SHIP, SHIPPED) +.addTransition(PAID, REQUEST_REFUND, REFUNDING) +``` + +### 对比总结 + +| 维度 | if-else | 状态机 | +|------|---------|--------| +| 可读性 | 嵌套深,难以理清 | 声明式,一行一规则 | +| 可维护性 | 新增状态需要改多处 | 新增一行 `addTransition` | +| 安全性 | 容易遗漏边界情况 | 未定义的转换自动拒绝 | +| 可视化 | 难以从代码推导出状态图 | 可直接导出/打印转换表 | +| 可测试性 | 需要覆盖所有分支组合 | 可针对每条转换单独测试 | + +--- + +## 扩展思路 + +如果想进一步学习,可以尝试: + +1. **持久化**:将状态存入数据库,重启后恢复 +2. **并发安全**:给 `fireEvent` 加锁或使用 `synchronized` +3. **历史记录**:记录每次状态转换的日志(审计追踪) +4. **子状态机**:比如"退款中"内部还有子流程(审核→打款→到账) +5. **超时调度**:结合 `ScheduledExecutorService` 实现真实的超时自动取消 +6. **Spring 集成**:用 Spring Statemachine 框架做企业级实现 diff --git a/out/com/example/fsm/OrderFSMDemo.class b/out/com/example/fsm/OrderFSMDemo.class new file mode 100644 index 0000000000000000000000000000000000000000..1892a55e5758231af73be8e93438e78cf4e173ea GIT binary patch literal 7599 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 literal 0 HcmV?d00001 diff --git a/out/com/example/fsm/core/Guard.class b/out/com/example/fsm/core/Guard.class new file mode 100644 index 0000000000000000000000000000000000000000..43fd7cfa403ab284d1b436079bb2312dd290e87a GIT binary patch literal 309 zcmZXPKT88a5XIj_y-UtN*jQ*|dp2%wRFFVK5CkI@c9V4yHtg-n-fr--S@;3`P~zU% zh^c1gH;*^-zP>*`0bJpr#DH+3eWT=^n>Q||qx$~;eth;q7|qPmaZDj=iu!5N!}@Y^u_?daM1*SQQ_wOsHXBs^H|O0U z!trBr(KPaGS~ItDg;u$FN_AZ+FE0Oe>qDDqt9X@Fto RzugE`Pj<1FYp2);egL5ybXuS6 zs54G|b$s)|Cw;0o+GYT4wL0UB`sACAuLwT*A2^=7*^(Pr5jsw02>0ZE=bZ05=iGaL z{CnjlfFY#)sDn>Jy^IDlG6XKD7uA@dn)9)<=?hxMW@sGLP2C=5@P#8Y5}Fv=olPZM zH)7*zabZf$`ys*Cj26feT4n4*8$%>xLU#MMFEm_K{w|~eF-NRx;&YR z`T8+{$GD@%8M;MBf|Aap@*#@MFf=Fic~iAZ1sXD6DycA}gtnz!gJj%lN&HK%RO`r5 zsf3b>E5pu*p+0V9ok6BapDyLnTEQLdp>1hK_D~lFB}}pQ01;DTc!#5Tro_@yU8j!* z9U|TvDg#p4ao1_QxlM$-o8&vw>Yb|CDR)j=s(9wVH27aAp5HUYJ*Y`6Myf3K0IcnX zhBUu|7!K3*;MonLf)ulr)|o|`61SK8cY~`9dB9L}>8#pQa}4(M)39&TOT4o9{4&f8 zCQof?6z+XkNo+SK$Q&2V?uk7tIB-o2{-jkZWVA7zU%u@k>*M?kWN4b!iuSlwq-!8B zYFT!XuI&7jW-nOTVp}85`tcl|m+*p&BvN!1RZmXcyl5?Iu}OzEA=EiFV_OBflv{G* z>SvL^3#tSdSIw-U6?-NvYq6AfPbcTfagR%ZQ4KJ4Ij@P^cmHE0}qH{gRRl`W? zwx$F?XYCP@kt?+ba!n-POZo<^Oc>poH*_?7DM8`|+Z611XP zw7dSt0)F013q zY(-dXLGY5v`(!d9@B8uj_6`7-(D9&za3)RS)2m39G5J%Q_|l~0M@~4p6LPLJ9j`<< z7kWlRWgTfsJ=jKQEyRoPW9I(DWI@s)G>k5EKO@|Bhrhdm|B;h1LN!t|EnJooLbE%H zf>AHX*D%OWA1|Ww_1V1*EZo4Z)~_PeB9o<(`YPrZ+eKGS^KT&>JZ0LcgvQFMNle#T z8@5gvZ4nL&6Z@H#ITrCiJ4&ZQQh-poC+lvEbsn@3j{b}Sp+3-Te;5mEiJ#~c-2Ta? vjDWDk5d$Env;$?<9;*^mSbguRP+O5*sB_Ib_P91ytO;$dHD>HG?3b%w_fDZ6 literal 0 HcmV?d00001 diff --git a/out/com/example/fsm/core/StateMachine.class b/out/com/example/fsm/core/StateMachine.class new file mode 100644 index 0000000000000000000000000000000000000000..ede2981606ef1d1ad63458e22f3c9a9ebf9824dc GIT binary patch literal 4501 zcmb_f?SB;26@G5A$!s@d*-A6oPQUMw2fDwoqR!?B#w(Vxv7AUTZSP{Ekps=#4 zy#!vAXz=MMg)Xop9FGOeK_k{5H3QwrSRfovn1PUO*k+3n?u}TcfF4dI5~gLVRqoK7 z-J;P-#e&l0bRA`wAy8%~3@aJ2BXO(E*cPRBSz0`0N1}liqd%yjTws&9368pPoN~qRnsyP$*E11h7=B#!jA8amzL$R&s+mBrqqRfldv#3oOo}&=ifDJw{aF(>NG5`(^lE zEEjm>lOwNAyz|nP!+WmmKQpm&*VT(}Obj3U^un(3i!YAv8%{5--m*No_0tOnua3Mn z{@n8)pFi;N*qO}Q#L1%*uk0H?IQq%c!?cptzdEvKeD9%u?tIFJJ5Z^iN=G$na&jNC z6A`P2x$llAVg{kO&mBqnZE(wtP`J@?q4n73% zbsgWpT>@TaXMM^@bO|h~tZL4ysopcq-^4u{?$vRh%+7+Fi_)q&U_?`_TrYwGRSs6i zj*q{!cYOHx#F3-Kb>hGaSH_Mo0qM62X6e~#9rd`MHAm0Zgk}C1(al^-3(&VUSgWBy z$G6caurRN08Dp9`XogcZ8zOH_wM!?~>v#Z7X(!r@geu{3I+3k3)o-QPfMyLXI$E(& zV0yOnWoHnFh;1eeJDw1jo6%N{)8wqv^9S*ehLDanJSWCg zv02A=(IMcm)bi|%Z5P?FySE73ofio=sVq|u4ZWl(Q#viXycC_-s^bxSpE1QPmjugX zgS&j4PIlq)IaaFLC4PpEZL**Wjjk>qx?pPP*3pAr<|m6rL!6zhs(}>bHDmquP)PF1 z%uMJCRxT+z+ODHd8Yu2FrI<7u(_tY_5=>dKcvqx5A~{i+sBi+Bvf!k7>bV4uA)z6u z!$vB%w}%K=jK0TH^kq&OhDbcHi7ii(PMAh4=*15Nmam@rt8nwr)uXRhOubp{!w`O` z;c*>1WQ(V%IoBMkprZvOgr5n_iy3`pL);1*_U4G)%e$m)STgkVqFrXfheIeNQG_BrmSLw7q?N+95LvG+?LY?ZIxb$n(iqH7 zxLK-}T5XNNwuWH6`j=}~_G?{!6Sr#dT*CrO-CeD?E!Wv#RXgdXA>PIK%9=Thw5DR) z%!D&pbXMkx?#qZ*?pnsOY{;2=iYluV#3kL4gxNU2;%A55Bmg%hN^8@IOt_EruUyyg z`wVR>K^ZxdS((OM%9oOsGQ22ITlvM{R`4}`L1lNYq)X-UWE?OeQAuGceD*-@`NEd3 zV+{4I!`X^2vL)rDOxu>OUx8I@?EkymX>dnfsMgh75;EyweX#MyW$8kLd)YZ2>Oaq( z?)U}Tzl`l&{yQEC_(JhiB5baU$XR)&yMgYMyv9;!HIsIWDeInq&sZCe+er@D{Vk^5 z8}CY%6=TeY3%ID^T}fH*2`p2Ds46RB4aEE8JTIrgG>fe>!W<1a+rI3sT}Lo&^Z~=_ zikit~&GC3&s^1|HWzXR>S0FixZA1NL9`ReunU0ADBN`2Hbm7_cN77kEh2W=0lf}uP zA!;O(@-&#G@W~w;1d7y|LY{ZXQNsu9%o{++7n_C;IfDLgQJzL=Ah3iVCj~qk3i->g zCkVX4rve;7x%~Q>dVZD9{DPT!{v|X@72#L>JxYzWv{1M)LPGk1ld@FE_zjLVv+yf8pMG1%C>8Ry78FY63HKl%oCdck=d=iu{sy+qn#FU9* zwYbJn`7NJb!$?|1tf6aON`6R{X;N(z^9yhZ{?=8OF>O`NC>9mqFcyztX-E08qKhc# zD3oHM4o^+!99Epg%J-y{QVP_1{GK!T+GX6Kt}91zx77A4?OKo0_IomV(oSuWl&rgq z>FP?2Zxx_g-mjCeoWkr;e1{BJ>@Ob0Cc$HGiM%5pT|#HA=GXj1HPQ|hzgL8jYk!x> z6)5D^G9y3>zbZFkF*e~Yj<7+rV?8>sm9u6PTad(~oPNTbU%GG*z3iVpoZ*}I5yM|O zy1h;di%>j?#TxtzC*`L|U=s5+{B9DnG(7kj$Bc9h94D-A;7unJmAuMI(PadL=g|Ue z&gN8pvV&t?DN>I1Th#so_fOZy!@Np~+o_$Cl;)Zbpq)eb1WiT+&LJwZoDR2~B?3Io zaCcx1o?wwYsoeKMOu|q1ovluAtG6L*LYCs6s4WZZv6JX8A2@?Sx#cLH5E#Wyfo5)` zP~X5C1^Vt5LjAOa-#?1IQtY5W>#9t0c%#Hv3bg3=oWpbH@gfN7;K;Rqj9fb@_xU-W zvUysn3h){JKFiP8y?6-wIGgW>i33FBAo@A!$vn)1XA<)W`oFP4!*1UE3oAaO|0Oty zcX*ZYRPZYQykj`TUi!uLUGS!Z!YM|bPTXnwCNVrqI6+(rSYw?Gi#CYSdH%mdl)#T* c57)i;b8!i$S3my3>mfeB&;2j6SU!UPzhIaOS^xk5 literal 0 HcmV?d00001 diff --git a/out/com/example/fsm/core/Transition.class b/out/com/example/fsm/core/Transition.class new file mode 100644 index 0000000000000000000000000000000000000000..4f6340a5473f5a548adc9248bd946a99b6bf48ad GIT binary patch literal 2225 zcmah~T~pge6g>;JgXMEb#gmq&()N|+V;yr!lkrWrBLmfC9iQM+ z8g=@ndn9nRk?8fI`Wmo)rehJGlf!I>K`bRB?gyWZ9Nk`3`7a4{^P^$@uRJE0v2F_o zIb6evhE*NcvGzgjo4#w=9Rcms?DpmUD}l{;VBysmg;;DM!kFW&Zs^#=4S|K8c`R#= z-8TK7EdMo6o^RT|C-68<79lTkUDPBv6v#HMj&1sVmzO-*Y8q;-b6ri&KDeezt7cSb zb3NH6yqGhiRLwae_e{gG<+Fb8P`WMiuuIK;p6=+2YcfyFSF%CeQQ<&fCtjGKOJhYy zlod)J#-YS4`?eW%{aij?vw(q(oG%1*nxbJ{HGZg4MN`;T(D?%DbC7|+GHJ&2nZVju zgR@F1_Aq5W-Ty*FuR>sbyu}&ml<%Ci>t!N_uRLljfrCo(c1*9TP+A!Z4yqOH@Iqc%0hCTI29x=&b zyq10H9LsVTcKB{~&9?8jZv}phH%4R_lJ1%IQCE8F4aYg|pBS+)qx@r<4}h(=Co;kO zWz0U|oktWkv)gT2zBH!rgbgTGN~M?L=TfP&;+2q8%5ye>!ynGRP*`$vYQMuyQc1ehLxH9BZ|B+xUS+a*Yeo5?C*om-9lmso( z#Xq3^#zPoJCQ$A~kgq9ylL(m}h1`lD-x6{w5i&apF=Af6!}ke+&5uIvMv&X|VxW=$ zSr~=XBFG&=s<_KlUC<6Dc>=De^#8;%x9M=3<@TP%$0*SrYaLed9_|x5fd{B@S2zCy Re~;0il;i0s_VEIX{{qud6$k(T literal 0 HcmV?d00001 diff --git a/out/com/example/fsm/order/OrderContext.class b/out/com/example/fsm/order/OrderContext.class new file mode 100644 index 0000000000000000000000000000000000000000..8c131bf030cc2a76671960a0943cefed5fb806a1 GIT binary patch literal 2284 zcmZ`)TT@$A6#jNDoDdE)4YU+1HPE6EF42P00=4BL12;9{^)gtR!W-T(mdRLEI0yQJ4Ov-v+z}?xkTnis+75G)uK^16^ z8|jFCUrT4#^++<8ju>V_HzV_M9XB$TexC%2t(;B>1ZPV0QOita)`lfvgNh@PtuAXC ziG19e)6%-sA5{@VBR$a4Mm|HP;|2S)X5ER5rPe0&cq*-}+Z@MK9GBR-d`>sVG)55D z{WyUZ1+S?%iPr_1?aQrHT93>aaczAnOyFc^SAqZk5c`T#D&9ayz#ljD zqG`*aCVsTzw1P7#I`F1IlYM)I>jJ)9e$_HHndrXG>ft$FRhZf?b*ku+E-6{l$m zAcwl(_*w3{50iY5y^SY+OkrBVhm!fqf7-1Oq`>(qk*^dAqpU55t6U-wtzuhd>g2I{ zl~vbvEem+YjRc$I$V@7u&*jsry1AsSu2WN=&}F+i-NtjFG>YfCv}WFmvg)!ro4+xY zbAE~DlgU&(rOTG)P`N?NsHMg4&1zYP*}tY+_7IU%qv@b8%QGs_K+HmEV6c9u7ys&&v!ms+ zu}`)I+Spw#PGh-ps&eO&^U#)@f41aovn5;5mK;R3WcS;W9Zso2aE#Azo#p8&dAyWu z;pY(H?kyw|KVD6y8De7FvHHNMMfZDJWc9;o+{9)2@NEjdZYub0vh+ zM9rhdp&x{sJD@on-Xzr~0^4XSlD<_UZ6FZnW=uVt(Y-WUD3bIzB!M!LFiA?*@(&8= zr}Y3WuNE!OP%p3d6@uukv>0M2!$d41%2noL(6NxdI{v=>7>ef^JnkR)yUR|23=bH^ z5?3!=!ApXQYgl$jS}7&KOA_U0ZVHJjSY77ndR1tBCG-lRA0ft7x>n8_sD#cFbfYS? zsS+wfU8(B8Kqd4#p*L~MaiE>i%RJY}IvJ?Uj{ zxMFUZlxWODQ6G)T6a}KO8j7Z9OrdD~9Su@+Z)5ZsyulBmJGd+zE2KWix&v;LT0kNn literal 0 HcmV?d00001 diff --git a/out/com/example/fsm/order/OrderEvent.class b/out/com/example/fsm/order/OrderEvent.class new file mode 100644 index 0000000000000000000000000000000000000000..6faf2259d67e92dc45250714359dc08912a21515 GIT binary patch literal 2790 zcma)8`BxKH6#j-SLmVWI6~(=XO5&2bQCp~rAyjNwB!EqAYlkpmB$-K*fl4>Ab)hbG zsak98V(mF?Jw0ea0+rVKbN`9{LPGed|3OdhdlMi+PATV*oB8Iu_q+GK`|iu1|2%pO z;3ZtOAWOh%zaDa`$CXexs5*~CLry)?u11^SX0Mh?SeAOlpu<&-jZF=v;tB~X z*{cGRO-}pWE*@{SglADg`IWX-c_^TOHHYRd)rwNAwV*`8I;ksOhN(qk>nAyV#Ef?aCE5dk~O(!5@>-0)E^0ijexFM34c*S1uAKuHSYa%;VC_tI&PPQ-Pl9h7E(G@m#+C0<6yuz zO5dV}q8U*EJ2PZDJD2pTQ{hGAif~cQiEH=AMsJF6Qx#*sK8W|n;{6vAeU}ML{QQ_@ z`!ZXeeUx(nH3k%+_O zZsN(<>BQ$T5sH9vQ!8<|H!*acvNiccNd9rwBolMui_4T)DjzN(Vu^@$wgT4%llO-w zdV6UTL{NLDR)8(kPzpCWB9@5=kc&y4=)FY}B02@EG*>v@e<$(nh>4q=Nyn8Uv?<5L z`K#ljUx^5lmgzY0(_s9|uhU&vJ47TzVX!mLj~+GQ+A{n9IQ6tglMXp9y`4@?p(miJ z^zv2a14vZ=gd{jIIa;S)UF1aO(`S{2tf89+ zhQ3z%gQ@wDU1o0;ustm(n|r9I`?+!Iza&<~~YQ-eEw=A=Tnlwi;sFw_bW=V|x}WrCf$uU6C4 z2%pz#)Phf`on}>+DQOEXQ-}XIm0HfMaL*QMTsuuvX-_e`mjSYL)pAQWEVp#4a!VH} zw{&}QOE)IBbUkuQcOgZR**(B5jd;Mel--2%Ors86Fi5YPo=V8FKkEQi#jxfHN*~aZ zfOqIKkH0G*{ze?1;%9NxDhrMgls$nko1ewTF|v64a%59Il#OG<@?mUtY#PE=M{&*& z%72;Ck+SCQUo%%dz9P*y#JWZ_-XdJmN;?G%D;Sv3i!c3pdHY=Md#AK^>kY^0+w&nj0!IwRNY_Bh802Z$=cYqF% zFK++^UY}(EHm^?{z(TLjMoQu(Lo{~RvR6C3CkaS2yY{4Yq@Bq0C* literal 0 HcmV?d00001 diff --git a/out/com/example/fsm/order/OrderState.class b/out/com/example/fsm/order/OrderState.class new file mode 100644 index 0000000000000000000000000000000000000000..cf9d5db038887663d45c5dc235294969a83c4ea4 GIT binary patch literal 2768 zcma)8{ZkWX5Pl8rMRNDQ!t4h1?p{Ty2)p_0=d@%#LN9ur(FL1tiuuj6oM z(BEaEOC>Bb(Ux|fKNvL86%tl*^oYN$yS+8gevr9ZC1JHi%TQBwt%Pc6$8FX}v3WN*Pu>F-7b5bo8E5BZk+3TGZK5FTn-3 zfJHM3er+gDo`;p#klJxVz|Q&tYhO%BCcOnf$Vf!Bes7@%M!@;EN@&0~0p-n6Eo$r& zu)L7UvJAL-*fP8%VLNuv%J)T+gE3{KU5QiZ@?1|$`DF<^O=UG1Nkj*YsIEB(e?`JB z?52Ixxb=&PTmED!E3brR>=m#gt_-L?U5hBj(Wr5fu9AkL8A$=VibOg$mcpzpdJ$zJ z_ESM*9-qrzyE8R;L&O0AHM!9rGMB%}jHffHo0-Xn&mUcSdiw(DO#N{;Gdf0U+20FE$&6W}&ou@FWtWm8|9kMemMm_|B?!O|fHu9mkonV;So&5iz;y*9=fDn%Tbj=@B_Xc_V`6e{O=a(B*7 zO^%C@MI5!f^H4?5S#F$JLJmHEl%iZrlWgjGZtPt4+p*mFUo#hfF%x|L=z@r4BHo@( zHBT6p3)FmtM@HygTnwCJ)q60pIQz z2fEKIz!B1i5)rj6$`Y=e86w+Q6BOV)pzB7`NGOAcRpX@Im#i$uCk}jy&+ItEI>+Z! z$TR&istxM{s>ck{Y$k0=#LyEX0&Wy8gr#Vb9#XWvn3}8!>iWRYV3CemklG7|T_b~P z5%psQ`c{iC>`oSiPl?4sQA72L_>$VBP%pRyNC8b#6TGvlNjtuxa+;G}v7qfp38?;W zRVq0%!~NT-aBHdeF4$ki#xy{ZhAx*hYq_NP$|a3dE@_BzNn?{s8kEE!vq`}v4FSNp zip@&8r@n_cV&q4oyNx8-$C1XGF|2=r4R`5IfKLDA{8=^WyD*s7FX5;|7Tgoq`~<>W z{SwxjQNsC~P)d1FH0~{H#<9iS@BkinmF)ounmjh!TW@iAVI#8 z*lqb*NswyPV>m5W=Ntl!n5#G`$Fg%hGgk!sTf!42A7!I74AEjU}P5lc41!2&)RsX0N7Q z8O8?|`(J$jfUA?lE$=IpUzJ}tSsQs8Bcxw~5AhL6krMrweqZ1$-LYPM4(D+J GRsRAww?L-= literal 0 HcmV?d00001 diff --git a/out/com/example/fsm/order/OrderStateMachineFactory.class b/out/com/example/fsm/order/OrderStateMachineFactory.class new file mode 100644 index 0000000000000000000000000000000000000000..20f3088092e13231c436598438594ae4ca3bbd6a GIT binary patch literal 5107 zcmcIo33yc175*=Q%w)KMG(uG>5HLzYg#i_d3{bNS5F1F$44|NemzkGjV3u^|4T5bo zL68KAfwIV^K#Ubs0t|G(G0=UpcHj5K%p~cS?t8W8zV|YDnMnfnYxCuM@1FC{IrrSN z{P*5@?akN~0E<|L1*4E*!f1{$$YjXbAZ?PYUddl;t*G80yA+1Zg&x00S-Ta@yiUR>afS_mV*ql3JS}w zs*qo-iQBR;4&zP8;dlpf8S+v!13@xstq@OjLI(5dqB7^Y%AyAtrl(DmDsHo20w$X9 zPLAtw14Ch|{j{a@d&a;;L_CsLpLbC~`9Rq0{;<#X_SQKG?=s;gj>(uZ@;dFCWItsj zvmznLQ+w@|d?O_DcEnPQkSTw<1nrJ&oE3x*jS1t_F! z%9~|ZSSU>$PN~SHcmjSkzVGI^4YL@sCAZrdl>8wITfon7AT4645lo|pOpoSYTCUa9 z>2AE;R9HFYh&+sTEGw%NHF!J69U_1x!Kk!T3`b#xD4x5-;@-pX$p2_D|E0rsb1V=J zo2u+3_OexW;qyX{MZ#yZ&LgKYFe3|-a1Te3kT#c8EUzrLJ5})#j#43>)z2jP(VF~H zj%7kVv!rN6iM?DHzn9~EnzYI)7a+3#Cv zcR1Ho*_W(bQ7X)>f?M*6ON4WnEEaWmSc?%!fGMuP;-4 z5{_hPN)ku4P|DVf(scLQOUNCImO7cE2DJ=hRBU3nWw=^4LK(G_2OCV-$l--=5LP<2 zgcO-r8G$hM=R~b|JppTF(BoGeN>G-3HZuYYrFnU4<2z4wT|W9yUT;G~;^iyd`_A{A zx!BY8Qtzqr@gvXnb?)lvXp6V(Pc*j0+grLW#o`wuiG63fUOm{oGn!~8br?z{`?=Uy(P{ z!m@*MP1x@)mS}@;$-=^iI6jO=sd8#%rN|cu3wXMLf~mUXlaxAZv8T3Fc6oe~mk^-= zjV5g6cpTdX$4~W&oEDHISJW`vYIIuLiU?H)U{+s;@ZkxL9m0okSz>FB)<}3m~Wg%H<~`pFf+GsJcU+<@icRl8k#Cu3JHLl z&>sVJE7k4hf@6>H=p!lX>rYDh#JR}E9y9hcSPhk<)k9Bod*bP6vS504yhxQ5Z#|HR zHc^q7@ifC^gOXNOR80nvLxb(=A|1ODO}qO#8xz}(^_*=>Y(3e1cw5hf(_OEeRqY;O zxY=Nr*zt}i+3h~j(bLhM*ca`&@Pb;U591k*XGL|5^+~~vjyg|$z3evQ_~2RN`=5yK zZB!lG6W=2GD1(o3ynvI`4xW(y z$q}xp@whzNo>@@1#)KCcW{*(l#%^CCxs*U~3vC1?0XJ=T2$0e2efuMfKaZ#myEU2GwL{;i%g8Dhf$5iSKL3MFl zP^pBVx;f6PRIi|Z$q`YhUkmCt9A{Ok&w}6K_a^*7!1o^+^3xKnVYs%(B(dY*|3A6< z-m%tD4E-@Tuu>Cc#-AA$8&F(3j1ca}cOD}ICNw4vok#LmUVQuGw8_LT?~OONbX`8w z+xCpcG~=%f&NLg^frbnuT6cHF+WR`2bbvp4vbSMhUuQEJ>ptBazp_2vazN}H9VZgU zFA>JapE?#la=fqeFf-%t48>Qse04kjWUv|RB$}V2BUf^jMD`wPA6R1ZDO#U#iFDN~ z^ZVssiB}4RWI8UG^xLTkZ_%kStq6u{ToYq-!x~wXHWRJO>8^H)?ib=On{HinC#UC2 zL`|kQqxYDZ7s1ZafZ=6&iqAj}9eYosmJ~^pqL)cLpz{(;5oAYk9X%tsF@mWvOb4~p zt)S+ZNpu9WBbXb*oq{osh7rt&@=_vdlwxSMuQIB?Pp+4V6sPH6#%p`P><|v%Wv4}1vdm@n2h$CW_6+vCD zKZ^R)U^bCBL4Yp4m@ZC2e9FuyHjxUwK6nX_(Q7-sVrUW`8*GdtdEimHK0QV`Yam+$ zF1oD(ed-*|&aN1Cr)X>GR7i^eYFMqkLuz?Ri$G4-8nn!PgIv@mSx@4GkVAE!$U7-I>!0;vk$0*$RAH0P-I{*Lx literal 0 HcmV?d00001 diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..58249a1 --- /dev/null +++ b/run.bat @@ -0,0 +1,25 @@ +@echo off +chcp 65001 >nul 2>&1 +echo. +echo ======================================== +echo 编译并运行 订单状态机演示程序 +echo ======================================== +echo. + +if not exist out mkdir out + +echo [1/2] 编译中... +javac -encoding UTF-8 -d out src\com\example\fsm\core\*.java src\com\example\fsm\order\*.java src\com\example\fsm\OrderFSMDemo.java + +if %errorlevel% neq 0 ( + echo 编译失败!请检查 Java 环境。 + pause + exit /b 1 +) + +echo [2/2] 运行中... +echo. +java -Dfile.encoding=UTF-8 -cp out com.example.fsm.OrderFSMDemo + +echo. +pause diff --git a/src/com/example/fsm/OrderFSMDemo.java b/src/com/example/fsm/OrderFSMDemo.java new file mode 100644 index 0000000..8f48761 --- /dev/null +++ b/src/com/example/fsm/OrderFSMDemo.java @@ -0,0 +1,214 @@ +package com.example.fsm; + +import com.example.fsm.core.StateMachine; +import com.example.fsm.order.OrderContext; +import com.example.fsm.order.OrderEvent; +import com.example.fsm.order.OrderState; +import com.example.fsm.order.OrderStateMachineFactory; + +import java.math.BigDecimal; + +/** + * 订单状态机演示程序 + * + * 本程序演示三个典型场景: + * 场景一:正常下单 → 支付 → 发货 → 收货 → 完成(Happy Path) + * 场景二:下单 → 支付超时自动取消 + * 场景三:下单 → 支付 → 申请退款 → 退款成功 + * 场景四:余额不足支付失败(演示守卫条件) + * 场景五:非法操作(演示状态机的防护能力) + */ +public class OrderFSMDemo { + + public static void main(String[] args) { + + System.out.println("╔══════════════════════════════════════════════════════╗"); + System.out.println("║ 电商订单状态机演示程序 ║"); + System.out.println("╚══════════════════════════════════════════════════════╝"); + System.out.println(); + + scenario1_happyPath(); + scenario2_timeout(); + scenario3_refund(); + scenario4_insufficientBalance(); + scenario5_illegalTransition(); + } + + /** + * 场景一:正常完整流程(Happy Path) + * + * 待支付 → 已支付 → 已发货 → 已收货 → 已完成 + */ + private static void scenario1_happyPath() { + printScenarioHeader("场景一:正常完整流程(Happy Path)"); + + // 1. 创建状态机 + StateMachine fsm = OrderStateMachineFactory.create(); + + // 2. 注册监听器 —— 每次状态变化都会被记录 + fsm.addListener((from, event, to, ctx) -> + System.out.println(" [状态变更] " + from + " --[" + event + "]--> " + to) + ); + + // 3. 打印转换表(方便理解) + fsm.printTransitionTable(); + + // 4. 创建订单上下文 + OrderContext order = new OrderContext("ORD-2024-001", "Java编程思想(第4版)", new BigDecimal("99.00"), new BigDecimal("500.00")); + System.out.println("创建订单:" + order); + printState(fsm); + + // 5. 依次触发事件,推动状态流转 + System.out.println("\n--- 买家点击[立即支付] ---"); + fsm.fireEvent(OrderEvent.PAY, order); + printState(fsm); + + System.out.println("\n--- 卖家点击[确认发货] ---"); + fsm.fireEvent(OrderEvent.SHIP, order); + printState(fsm); + + System.out.println("\n--- 买家点击[确认收货] ---"); + fsm.fireEvent(OrderEvent.RECEIVE, order); + printState(fsm); + + System.out.println("\n--- 买家完成评价 ---"); + fsm.fireEvent(OrderEvent.COMPLETE, order); + printState(fsm); + + System.out.println(); + } + + /** + * 场景二:支付超时自动取消 + * + * 待支付 → 已取消 + */ + private static void scenario2_timeout() { + printScenarioHeader("场景二:支付超时自动取消"); + + StateMachine fsm = OrderStateMachineFactory.create(); + fsm.addListener((from, event, to, ctx) -> + System.out.println(" [状态变更] " + from + " --[" + event + "]--> " + to) + ); + + OrderContext order = new OrderContext("ORD-2024-002", "设计模式:可复用面向对象软件的基础", new BigDecimal("68.00"), new BigDecimal("500.00")); + System.out.println("创建订单:" + order); + printState(fsm); + + System.out.println("\n--- 30分钟后,系统触发超时事件 ---"); + fsm.fireEvent(OrderEvent.TIMEOUT, order); + printState(fsm); + + System.out.println(); + } + + /** + * 场景三:申请退款 + * + * 待支付 → 已支付 → 退款中 → 已退款 + */ + private static void scenario3_refund() { + printScenarioHeader("场景三:支付后申请退款"); + + StateMachine fsm = OrderStateMachineFactory.create(); + fsm.addListener((from, event, to, ctx) -> + System.out.println(" [状态变更] " + from + " --[" + event + "]--> " + to) + ); + + OrderContext order = new OrderContext("ORD-2024-003", "深入理解Java虚拟机(第3版)", new BigDecimal("129.00"), new BigDecimal("500.00")); + System.out.println("创建订单:" + order); + printState(fsm); + + System.out.println("\n--- 买家支付 ---"); + fsm.fireEvent(OrderEvent.PAY, order); + printState(fsm); + + System.out.println("\n--- 买家申请退款 ---"); + order.setRefundReason("买错了,想要第2版"); + fsm.fireEvent(OrderEvent.REQUEST_REFUND, order); + printState(fsm); + + System.out.println("\n--- 卖家审核通过退款 ---"); + fsm.fireEvent(OrderEvent.REFUND_APPROVE, order); + printState(fsm); + + System.out.println(); + } + + /** + * 场景四:余额不足,支付失败 + * + * 演示 Guard(守卫条件)的作用 + */ + private static void scenario4_insufficientBalance() { + printScenarioHeader("场景四:余额不足,支付被守卫条件拦截"); + + StateMachine fsm = OrderStateMachineFactory.create(); + + // 注意余额只有 50 元,但商品需要 999 元 + OrderContext order = new OrderContext("ORD-2024-004", "MacBook Pro 14寸", new BigDecimal("9999.00"), new BigDecimal("50.00")); + System.out.println("创建订单:" + order); + printState(fsm); + + System.out.println("\n--- 买家尝试支付(余额不足)---"); + try { + fsm.fireEvent(OrderEvent.PAY, order); + } catch (IllegalStateException e) { + System.out.println(" [拦截] " + e.getMessage()); + } + // 状态应该还是"待支付",没有被错误地改变 + printState(fsm); + + System.out.println(); + } + + /** + * 场景五:非法状态转换 + * + * 演示状态机的"防护能力"——不允许不合法的操作 + */ + private static void scenario5_illegalTransition() { + printScenarioHeader("场景五:非法操作(在待支付状态下尝试发货)"); + + StateMachine fsm = OrderStateMachineFactory.create(); + + OrderContext order = new OrderContext("ORD-2024-005", "算法导论(第3版)", new BigDecimal("128.00"), new BigDecimal("500.00")); + System.out.println("创建订单:" + order); + printState(fsm); + + System.out.println("\n--- 尝试在未支付时直接发货 ---"); + try { + fsm.fireEvent(OrderEvent.SHIP, order); // 这是非法的! + } catch (IllegalStateException e) { + System.out.println(" [拦截] " + e.getMessage()); + } + printState(fsm); + + System.out.println("\n说明:状态机保证了业务流程的正确性,不可能跳过支付直接发货。"); + System.out.println("\n当前状态下可执行的事件:" + fsm.getAvailableEvents()); + System.out.println(); + } + + // ===== 辅助方法 ===== + + private static void printState(StateMachine fsm) { + System.out.println(" ★ 当前状态:" + fsm.getCurrentState()); + } + + private static void printScenarioHeader(String title) { + System.out.println("┌─────────────────────────────────────────────────┐"); + System.out.printf("│ %s%s│%n", title, " ".repeat(Math.max(0, 47 - title.length() - getChineseCount(title)))); + System.out.println("└─────────────────────────────────────────────────┘"); + } + + /** 计算字符串中中文字符的数量(中文占两个宽度,需要额外减去) */ + private static int getChineseCount(String str) { + int count = 0; + for (char c : str.toCharArray()) { + if (Character.UnicodeScript.of(c) == Character.UnicodeScript.HAN) { + count++; + } + } + return count; + } +} diff --git a/src/com/example/fsm/core/Action.java b/src/com/example/fsm/core/Action.java new file mode 100644 index 0000000..fb76cd7 --- /dev/null +++ b/src/com/example/fsm/core/Action.java @@ -0,0 +1,20 @@ +package com.example.fsm.core; + +/** + * 动作接口 —— 状态转换时要执行的业务逻辑 + * + * 例如:支付成功后扣减库存、发货后发送短信通知等。 + * 使用泛型 C 代表上下文(Context),这样不同业务可以传入自己的数据对象。 + * + * @param 上下文类型,承载业务数据(如订单信息) + */ +@FunctionalInterface +public interface Action { + + /** + * 执行动作 + * + * @param context 当前业务上下文 + */ + void execute(C context); +} diff --git a/src/com/example/fsm/core/Guard.java b/src/com/example/fsm/core/Guard.java new file mode 100644 index 0000000..f729046 --- /dev/null +++ b/src/com/example/fsm/core/Guard.java @@ -0,0 +1,25 @@ +package com.example.fsm.core; + +/** + * 守卫条件接口 —— 判断某个状态转换是否允许发生 + * + * 举个例子: + * "待支付 -> 已支付" 这个转换,需要守卫条件检查余额是否充足。 + * 如果余额不足,即使触发了"支付"事件,转换也不会发生。 + * + * 守卫条件是状态机中非常重要的概念,它让转换不再是"无脑跳转", + * 而是可以根据业务规则动态决定是否放行。 + * + * @param 上下文类型 + */ +@FunctionalInterface +public interface Guard { + + /** + * 判断是否满足转换条件 + * + * @param context 当前业务上下文 + * @return true=允许转换, false=阻止转换 + */ + boolean evaluate(C context); +} diff --git a/src/com/example/fsm/core/StateMachine.java b/src/com/example/fsm/core/StateMachine.java new file mode 100644 index 0000000..3777995 --- /dev/null +++ b/src/com/example/fsm/core/StateMachine.java @@ -0,0 +1,223 @@ +package com.example.fsm.core; + +import java.util.*; + +/** + * 状态机核心引擎 + * + * 这是整个状态机的"大脑"。它做三件事: + * 1. 维护当前状态 + * 2. 存储所有合法的转换规则 + * 3. 接收事件,查找匹配的转换,执行状态跳转 + * + * 工作流程(当外部触发一个事件时): + * ┌─────────────────────────────────────────────────────┐ + * │ 1. 根据 "当前状态 + 事件" 查找转换规则 │ + * │ 2. 如果找到了转换规则: │ + * │ a. 检查守卫条件(Guard),不满足则拒绝 │ + * │ b. 执行转换动作(Action) │ + * │ c. 将当前状态切换为目标状态 │ + * │ 3. 如果没找到:抛出异常,说明这个事件在当前状态下 │ + * │ 是非法的 │ + * └─────────────────────────────────────────────────────┘ + * + * 用法:通过 Builder 构建(见下方内部类) + * + * @param 状态类型 + * @param 事件类型 + * @param 上下文类型 + */ +public class StateMachine, E extends Enum, C> { + + /** 当前状态 */ + private S currentState; + + /** + * 转换规则表 + * + * 结构:Map< 源状态, Map< 事件, 转换规则 > > + * + * 这是一个两级 Map: + * 第一级 key = 源状态 → 得到该状态下所有可能的转换 + * 第二级 key = 事件 → 得到具体的转换规则 + * + * 例如: + * transitionTable.get(待支付).get(支付事件) → Transition{待支付→已支付} + */ + private final Map>> transitionTable; + + /** 状态变更监听器列表(观察者模式) */ + private final List> listeners; + + /** + * 私有构造 —— 只能通过 Builder 创建 + */ + private StateMachine(S initialState, Map>> transitionTable) { + this.currentState = initialState; + this.transitionTable = transitionTable; + this.listeners = new ArrayList<>(); + } + + /** + * 获取当前状态 + */ + public S getCurrentState() { + return currentState; + } + + /** + * 触发事件 —— 这是状态机对外的核心方法 + * + * @param event 要触发的事件 + * @param context 业务上下文 + * @throws IllegalStateException 如果当前状态下该事件不合法 + */ + public void fireEvent(E event, C context) { + // 第一步:根据当前状态查找该状态下的所有转换 + Map> stateTransitions = transitionTable.get(currentState); + + if (stateTransitions == null || !stateTransitions.containsKey(event)) { + throw new IllegalStateException( + String.format("非法的状态转换!当前状态=[%s],触发事件=[%s],没有匹配的转换规则。", currentState, event) + ); + } + + // 第二步:取出对应的转换规则 + Transition transition = stateTransitions.get(event); + + // 第三步:检查守卫条件 + if (transition.getGuard() != null && !transition.getGuard().evaluate(context)) { + throw new IllegalStateException( + String.format("转换被守卫条件拒绝!转换=[%s]", transition) + ); + } + + // 记录旧状态(用于通知监听器) + S oldState = currentState; + + // 第四步:执行转换动作(业务逻辑) + if (transition.getAction() != null) { + transition.getAction().execute(context); + } + + // 第五步:切换状态 + currentState = transition.getTarget(); + + // 第六步:通知所有监听器 + for (StateChangeListener listener : listeners) { + listener.onStateChanged(oldState, event, currentState, context); + } + } + + /** + * 添加状态变更监听器 + */ + public void addListener(StateChangeListener listener) { + listeners.add(listener); + } + + /** + * 获取当前状态下可以触发的所有事件(用于 UI 展示"下一步可以做什么") + */ + public Set getAvailableEvents() { + Map> stateTransitions = transitionTable.get(currentState); + if (stateTransitions == null) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(stateTransitions.keySet()); + } + + /** + * 打印所有转换规则(调试用) + */ + public void printTransitionTable() { + System.out.println("========== 状态转换表 =========="); + for (Map.Entry>> entry : transitionTable.entrySet()) { + for (Transition t : entry.getValue().values()) { + System.out.println(" " + t); + } + } + System.out.println("================================"); + } + + // ========================================================================= + // 状态变更监听器接口 + // ========================================================================= + + /** + * 状态变更监听器 —— 当状态发生变化时收到通知 + * + * 典型用途:记录日志、发送通知、更新数据库等 + */ + @FunctionalInterface + public interface StateChangeListener { + void onStateChanged(S fromState, E event, S toState, C context); + } + + // ========================================================================= + // Builder 构建器 —— 用于流畅地创建状态机 + // ========================================================================= + + /** + * 状态机构建器 + * + * 使用方式: + * StateMachine fsm = StateMachine + * .builder() + * .initialState(MyState.INIT) + * .addTransition(...) + * .addTransition(...) + * .build(); + */ + public static , E extends Enum, C> Builder builder() { + return new Builder<>(); + } + + public static class Builder, E extends Enum, C> { + + private S initialState; + private final Map>> transitionTable = new HashMap<>(); + + /** + * 设置初始状态 + */ + public Builder initialState(S state) { + this.initialState = state; + return this; + } + + /** + * 添加一条转换规则(完整版:含守卫和动作) + */ + public Builder addTransition(S source, E event, S target, Guard guard, Action action) { + transitionTable + .computeIfAbsent(source, k -> new HashMap<>()) + .put(event, new Transition<>(source, event, target, guard, action)); + return this; + } + + /** + * 添加一条转换规则(简化版:只有动作,没有守卫) + */ + public Builder addTransition(S source, E event, S target, Action action) { + return addTransition(source, event, target, null, action); + } + + /** + * 添加一条转换规则(最简版:纯状态跳转) + */ + public Builder addTransition(S source, E event, S target) { + return addTransition(source, event, target, null, null); + } + + /** + * 构建状态机实例 + */ + public StateMachine build() { + if (initialState == null) { + throw new IllegalStateException("必须设置初始状态!请调用 initialState()"); + } + return new StateMachine<>(initialState, transitionTable); + } + } +} diff --git a/src/com/example/fsm/core/Transition.java b/src/com/example/fsm/core/Transition.java new file mode 100644 index 0000000..cb15ab9 --- /dev/null +++ b/src/com/example/fsm/core/Transition.java @@ -0,0 +1,74 @@ +package com.example.fsm.core; + +/** + * 状态转换定义 —— 描述"从哪个状态,经过什么事件,到达哪个状态" + * + * 这是状态机最核心的数据结构。每一条 Transition 就是状态图里的一条"箭头"。 + * + * 一条完整的转换包含五个要素: + * 1. source —— 源状态(箭头的起点) + * 2. event —— 触发事件(箭头上的标签) + * 3. target —— 目标状态(箭头的终点) + * 4. guard —— 守卫条件(可选,转换前的检查) + * 5. action —— 转换动作(可选,转换时执行的业务逻辑) + * + * 示例: + * source=待支付, event=支付, target=已支付, guard=余额充足?, action=扣款 + * + * @param 状态类型(通常是枚举) + * @param 事件类型(通常是枚举) + * @param 上下文类型(业务数据) + */ +public class Transition, E extends Enum, C> { + + private final S source; // 源状态 + private final E event; // 触发事件 + private final S target; // 目标状态 + private final Guard guard; // 守卫条件(可为 null,表示无条件放行) + private final Action action; // 转换动作(可为 null,表示无副作用) + + /** + * 完整构造:指定所有要素 + */ + public Transition(S source, E event, S target, Guard guard, Action action) { + this.source = source; + this.event = event; + this.target = target; + this.guard = guard; + this.action = action; + } + + /** + * 简化构造:没有守卫条件和动作(纯粹的状态跳转) + */ + public Transition(S source, E event, S target) { + this(source, event, target, null, null); + } + + // ===== Getter 方法 ===== + + public S getSource() { + return source; + } + + public E getEvent() { + return event; + } + + public S getTarget() { + return target; + } + + public Guard getGuard() { + return guard; + } + + public Action getAction() { + return action; + } + + @Override + public String toString() { + return source + " --[" + event + "]--> " + target; + } +} diff --git a/src/com/example/fsm/order/OrderContext.java b/src/com/example/fsm/order/OrderContext.java new file mode 100644 index 0000000..dff15ea --- /dev/null +++ b/src/com/example/fsm/order/OrderContext.java @@ -0,0 +1,103 @@ +package com.example.fsm.order; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 订单上下文 —— 承载订单的业务数据 + * + * 在状态机模式中,Context(上下文)用于在状态转换过程中传递业务数据。 + * Guard(守卫)可以读取它来判断条件,Action(动作)可以修改它来更新业务状态。 + * + * 这里模拟了一个简化版的电商订单,包含核心字段。 + */ +public class OrderContext { + + private String orderId; // 订单号 + private String productName; // 商品名称 + private BigDecimal amount; // 订单金额 + private BigDecimal userBalance; // 用户余额(用于演示守卫条件) + private String refundReason; // 退款原因 + private LocalDateTime createTime; // 创建时间 + private LocalDateTime payTime; // 支付时间 + private LocalDateTime shipTime; // 发货时间 + + /** + * 创建一个模拟订单 + */ + public OrderContext(String orderId, String productName, BigDecimal amount, BigDecimal userBalance) { + this.orderId = orderId; + this.productName = productName; + this.amount = amount; + this.userBalance = userBalance; + this.createTime = LocalDateTime.now(); + } + + // ===== 业务方法 ===== + + /** + * 扣减用户余额(支付时调用) + */ + public void deductBalance() { + this.userBalance = this.userBalance.subtract(this.amount); + this.payTime = LocalDateTime.now(); + } + + /** + * 退还用户余额(退款时调用) + */ + public void refundBalance() { + this.userBalance = this.userBalance.add(this.amount); + } + + /** + * 记录发货时间 + */ + public void markShipped() { + this.shipTime = LocalDateTime.now(); + } + + /** + * 判断余额是否充足 + */ + public boolean isBalanceSufficient() { + return userBalance.compareTo(amount) >= 0; + } + + // ===== Getter / Setter ===== + + public String getOrderId() { + return orderId; + } + + public String getProductName() { + return productName; + } + + public BigDecimal getAmount() { + return amount; + } + + public BigDecimal getUserBalance() { + return userBalance; + } + + public String getRefundReason() { + return refundReason; + } + + public void setRefundReason(String refundReason) { + this.refundReason = refundReason; + } + + @Override + public String toString() { + DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + return String.format( + "订单[%s] 商品=%s 金额=%.2f 用户余额=%.2f 创建时间=%s", + orderId, productName, amount, userBalance, + createTime != null ? createTime.format(fmt) : "无" + ); + } +} diff --git a/src/com/example/fsm/order/OrderEvent.java b/src/com/example/fsm/order/OrderEvent.java new file mode 100644 index 0000000..485b909 --- /dev/null +++ b/src/com/example/fsm/order/OrderEvent.java @@ -0,0 +1,48 @@ +package com.example.fsm.order; + +/** + * 订单事件枚举 + * + * 事件 = 触发状态转换的"动作"或"信号" + * + * 在状态机中,状态不会自己变化,必须由事件来驱动。 + * 比如:用户点击"付款"按钮 → 系统发出 PAY 事件 → 状态机将订单从"待支付"转为"已支付" + * + * 每个事件都有明确的触发者(买家/卖家/系统)和触发场景。 + */ +public enum OrderEvent { + + // ===== 正向流程事件 ===== + PAY("支付", "买家完成付款"), + SHIP("发货", "卖家确认发货"), + RECEIVE("确认收货", "买家确认收到商品"), + COMPLETE("完成", "买家完成评价,或系统自动完成"), + + // ===== 逆向流程事件 ===== + CANCEL("取消订单", "买家主动取消未支付的订单"), + TIMEOUT("支付超时", "系统检测到订单超过支付时限,自动取消"), + REQUEST_REFUND("申请退款", "买家发起退款申请"), + REFUND_APPROVE("退款通过", "卖家审核通过退款申请"), + REFUND_REJECT("退款拒绝", "卖家驳回退款申请"); + + private final String displayName; // 中文显示名 + private final String description; // 事件说明 + + OrderEvent(String displayName, String description) { + this.displayName = displayName; + this.description = description; + } + + public String getDisplayName() { + return displayName; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return displayName + "(" + name() + ")"; + } +} diff --git a/src/com/example/fsm/order/OrderState.java b/src/com/example/fsm/order/OrderState.java new file mode 100644 index 0000000..071a754 --- /dev/null +++ b/src/com/example/fsm/order/OrderState.java @@ -0,0 +1,61 @@ +package com.example.fsm.order; + +/** + * 订单状态枚举 + * + * 对应电商系统中一个订单从创建到结束的所有可能状态。 + * + * 状态流转全景图: + * + * ┌──────────┐ + * ┌───→│ 已取消 │ + * 取消/超时 │ └──────────┘ + * │ + * ┌──────────┐ 支付 ┌──────────┐ 发货 ┌──────────┐ 确认收货 ┌──────────┐ 完成 ┌──────────┐ + * │ 待支付 │──────→│ 已支付 │──────→│ 已发货 │─────────→│ 已收货 │──────→│ 已完成 │ + * └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ + * │ │ + * 申请退款│ 申请退款│ + * ▼ ▼ + * ┌──────────┐ + * │ 退款中 │ + * └──────────┘ + * │ │ + * 退款通过│ 退款拒绝│ + * ▼ ▼ + * ┌──────────┐ 回到之前的状态 + * │ 已退款 │ (已支付/已发货) + * └──────────┘ + */ +public enum OrderState { + + WAIT_PAY("待支付", "订单已创建,等待买家付款"), + PAID("已支付", "买家已完成支付,等待卖家发货"), + SHIPPED("已发货", "卖家已发货,等待买家确认收货"), + RECEIVED("已收货", "买家已确认收货,等待评价"), + COMPLETED("已完成", "订单已完成(已评价或超时自动完成)"), + CANCELLED("已取消", "订单已取消(买家主动取消或支付超时)"), + REFUNDING("退款中", "买家申请退款,等待卖家审核"), + REFUNDED("已退款", "退款已完成,资金已退回买家账户"); + + private final String displayName; // 中文显示名 + private final String description; // 状态说明 + + OrderState(String displayName, String description) { + this.displayName = displayName; + this.description = description; + } + + public String getDisplayName() { + return displayName; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return displayName + "(" + name() + ")"; + } +} diff --git a/src/com/example/fsm/order/OrderStateMachineFactory.java b/src/com/example/fsm/order/OrderStateMachineFactory.java new file mode 100644 index 0000000..520a5e7 --- /dev/null +++ b/src/com/example/fsm/order/OrderStateMachineFactory.java @@ -0,0 +1,141 @@ +package com.example.fsm.order; + +import com.example.fsm.core.StateMachine; + +/** + * 订单状态机工厂 —— 负责组装订单状态机的所有转换规则 + * + * 为什么要单独一个工厂类? + * 状态机的"骨架"(StateMachine)是通用的,不关心具体业务。 + * 而"转换规则"是订单业务特有的。把规则的组装抽到工厂类里, + * 实现了 "状态机引擎" 和 "业务规则" 的分离。 + * + * 如果将来要做"物流状态机"或"审批状态机",只需要写新的工厂类, + * 核心引擎完全复用。 + */ +public class OrderStateMachineFactory { + + /** + * 创建并返回一个配置好所有转换规则的订单状态机 + * + * 下面每一条 addTransition 都对应状态图上的一条箭头。 + * 阅读时建议对照 OrderState 中的状态流转图。 + */ + public static StateMachine create() { + + return StateMachine.builder() + + // ============================== + // 初始状态:待支付 + // ============================== + .initialState(OrderState.WAIT_PAY) + + // ------------------------------ + // 正向流程 + // ------------------------------ + + // 待支付 --[支付]--> 已支付 + // 守卫条件:用户余额 >= 订单金额 + // 动作:扣减余额 + .addTransition( + OrderState.WAIT_PAY, // 源状态 + OrderEvent.PAY, // 触发事件 + OrderState.PAID, // 目标状态 + ctx -> ctx.isBalanceSufficient(), // 守卫:余额充足才能支付 + ctx -> { // 动作:扣款 + ctx.deductBalance(); + System.out.println(" [动作] 扣款成功!扣除 " + ctx.getAmount() + " 元,剩余余额 " + ctx.getUserBalance() + " 元"); + } + ) + + // 已支付 --[发货]--> 已发货 + // 动作:记录发货时间 + .addTransition( + OrderState.PAID, + OrderEvent.SHIP, + OrderState.SHIPPED, + ctx -> { + ctx.markShipped(); + System.out.println(" [动作] 商品已发出!请注意查收。"); + } + ) + + // 已发货 --[确认收货]--> 已收货 + .addTransition( + OrderState.SHIPPED, + OrderEvent.RECEIVE, + OrderState.RECEIVED, + ctx -> System.out.println(" [动作] 买家已确认收货。") + ) + + // 已收货 --[完成]--> 已完成 + .addTransition( + OrderState.RECEIVED, + OrderEvent.COMPLETE, + OrderState.COMPLETED, + ctx -> System.out.println(" [动作] 订单完成!感谢您的购买。") + ) + + // ------------------------------ + // 取消/超时流程 + // ------------------------------ + + // 待支付 --[取消]--> 已取消 + .addTransition( + OrderState.WAIT_PAY, + OrderEvent.CANCEL, + OrderState.CANCELLED, + ctx -> System.out.println(" [动作] 订单已取消。") + ) + + // 待支付 --[超时]--> 已取消 + .addTransition( + OrderState.WAIT_PAY, + OrderEvent.TIMEOUT, + OrderState.CANCELLED, + ctx -> System.out.println(" [动作] 支付超时,订单自动取消。") + ) + + // ------------------------------ + // 退款流程 + // ------------------------------ + + // 已支付 --[申请退款]--> 退款中 + .addTransition( + OrderState.PAID, + OrderEvent.REQUEST_REFUND, + OrderState.REFUNDING, + ctx -> System.out.println(" [动作] 退款申请已提交,等待卖家审核。原因:" + ctx.getRefundReason()) + ) + + // 已发货 --[申请退款]--> 退款中 + .addTransition( + OrderState.SHIPPED, + OrderEvent.REQUEST_REFUND, + OrderState.REFUNDING, + ctx -> System.out.println(" [动作] 退款申请已提交(商品在途),等待卖家审核。原因:" + ctx.getRefundReason()) + ) + + // 退款中 --[退款通过]--> 已退款 + // 动作:退还用户余额 + .addTransition( + OrderState.REFUNDING, + OrderEvent.REFUND_APPROVE, + OrderState.REFUNDED, + ctx -> { + ctx.refundBalance(); + System.out.println(" [动作] 退款成功!已退回 " + ctx.getAmount() + " 元,当前余额 " + ctx.getUserBalance() + " 元"); + } + ) + + // 退款中 --[退款拒绝]--> 已支付(退回到支付后的状态,简化处理) + .addTransition( + OrderState.REFUNDING, + OrderEvent.REFUND_REJECT, + OrderState.PAID, + ctx -> System.out.println(" [动作] 退款申请被驳回,订单恢复为已支付状态。") + ) + + .build(); + } +}