本文最后更新于:2026年5月16日 晚上

深入理解事件驱动的有限状态机(FSM)与状态图(Statecharts)的设计原理,涵盖核心概念、架构模式、主流框架对比以及工程实践中的最佳实践。

1 引言

在软件工程中,状态机(State Machine)是最古老也最经久不衰的建模工具之一。从数字电路设计到网络协议解析,从嵌入式系统到现代前端应用,状态机无处不在。而将状态机与 事件驱动(Event-Driven)范式结合,便诞生了一种强大的架构模式——事件驱动的状态机框架

这种模式的核心思想很简单:

系统在任意时刻处于有限的若干状态之一,当外部事件到达时,根据当前状态和事件类型,决定是否切换到新状态,并执行相应的动作。

这看似朴素的模型,却能优雅地解决现实世界中大量复杂的状态管理问题。本文将从理论基础出发,逐步深入到工程实践。

2 理论基础

2.1 有限状态机(FSM)

有限状态机(Finite State Machine,FSM)是一种数学计算模型,由以下五元组定义:

$$ \text{FSM} = (S, \Sigma, \delta, s_0, F) $$

其中:

符号 含义
$S$ 有限状态集合
$\Sigma$ 有限输入事件(字母表)
$\delta: S \times \Sigma \rightarrow S$ 状态转换函数
$s_0 \in S$ 初始状态
$F \subseteq S$ 终止状态集合

FSM 的关键特性是 确定性(Deterministic):给定当前状态和输入事件,下一个状态是唯一确定的。这种确定性使得 FSM 的行为完全可预测,极大地简化了测试和验证。

2.2 Mealy 机与 Moore 机

根据输出与状态、输入的关系,FSM 可以分为两类:

  • Moore 机:输出仅取决于当前状态。每个状态关联一组固定的输出动作。
  • Mealy 机:输出取决于当前状态和输入事件。动作在状态转换时触发。

在软件实践中,Mealy 机更为常见——我们通常在转换(transition)上定义动作,而不是在状态上。但现代框架往往同时支持两种模式。

2.3 从 FSM 到 Statecharts

传统 FSM 有一个致命弱点:状态爆炸。当系统复杂度增长时,状态数量呈指数级膨胀。David Harel 在 1987 年提出了 Statecharts(状态图)来解决这个问题,引入了三个关键扩展:

  1. 层次化状态(Hierarchical States):状态可以嵌套,子状态继承父状态的转换规则
  2. 并行状态(Parallel States):多个正交的区域可以同时处于活动状态
  3. 通信机制:状态之间可以通过事件进行通信

Statecharts 是 FSM 的严格超集——每个 FSM 都是 Statecharts,但 Statecharts 可以用更少的图元表达更复杂的逻辑。

3 事件驱动架构

3.1 什么是事件驱动

事件驱动编程范式的核心是 控制反转(Inversion of Control):

1
2
3
4
5
6
7
8
9
10
11
传统轮询模式:
while (true) {
if (condition_A()) handle_A();
if (condition_B()) handle_B();
// 持续轮询...
}

事件驱动模式:
eventQueue.on('event_A', handle_A);
eventQueue.on('event_B', handle_B);
// 被动等待事件到来

在传统模式中,程序主动检查条件(轮询);在事件驱动模式中,程序被动响应外部事件。这种反转带来了几个重要优势:

  • 低耦合:事件生产者和消费者互不感知
  • 高响应性:无需浪费时间在无效轮询上
  • 天然异步:事件可以被排队、延迟、优先级排序

3.2 事件循环与 Run-to-Completion

事件驱动系统的核心是 事件循环(Event Loop),其基本工作方式:

1
2
3
4
5
while (running) {
event = getNextEvent(); // 从队列取事件
dispatch(event); // 分发给处理器
// 处理器必须快速完成(RTC)
}

Run-to-Completion(RTC) 是关键约束:每个事件处理器必须在处理下一个事件之前完成。这意味着:

  • 事件处理期间不会被其他事件中断(单线程视角)
  • 状态转换是原子的
  • 不存在竞态条件(在单个 Active Object 内部)

3.3 Active Object 模式

将事件驱动与 FSM 结合,最经典的架构模式是 Active Object(又称 Actor 模式)。其核心原则来自并发编程的最佳实践:

  1. 数据隔离:每个 Active Object 拥有自己的私有状态,不与外部共享
  2. 异步通信:Active Object 之间通过事件(消息)异步通信,不阻塞等待
  3. 事件循环:每个 Active Object 花费其生命周期响应传入事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌─────────────────────────────┐
│ Active Object
│ ┌───────────────────────┐ │
│ │ Event Queue │ │
│ │ [e1] [e2] [e3] ... │ │
│ └──────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ State Machine │ │
│ │ │ │
│ │ [State A] ──►[State B]│ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ Actions Actions │
│ └───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Private Data │ │
│ └───────────────────────┘ │
└─────────────────────────────┘

│ async events

Other Active Objects

这种模式在 Erlang/OTP、Akka(JVM)、QP(嵌入式)等框架中被广泛采用。它提供了真正的封装性——比传统 OOP 的封装更加严格,因为并发访问被事件队列序列化,无需互斥锁。

4 核心概念详解

4.1 状态(State)

状态是系统在某一时刻的完整快照。在 FSM 中,系统在任意时刻恰好处于一个状态(或 Statecharts 中的一组并行状态)。

状态可以分为以下几类:

类型 描述 示例
原子状态 不可再分的最小状态单元 “开”、“关”
组合状态 包含子状态的层次化状态 “运行中”(含"加速"、“匀速”、“减速”)
并行状态 多个区域同时活动 “播放中” 同时有"音频状态"和"视频状态"
终止状态 流程结束的标记 “完成”、“错误”
历史状态 记住上次离开时的子状态 暂停后恢复到之前的进度

4.2 事件(Event)

事件是触发状态变化的信号。它通常是一个不可变的数据结构:

1
2
3
4
5
interface Event {
type: string; // 事件类型
payload?: any; // 携带的数据
timestamp?: number; // 时间戳
}

事件的来源多种多样:

  • 用户交互:点击、输入、手势
  • 系统信号:定时器、网络回调、硬件中断
  • 内部生成:状态机自身的 raise / send 动作
  • 外部系统:消息队列、WebSocket、IPC

4.3 转换(Transition)

转换定义了状态变化的规则。一个完整的转换包含:

1
[源状态] + [触发事件] + [守卫条件][目标状态] + [动作]
  • 外部转换:退出源状态,进入目标状态(触发 exit/entry 动作)
  • 内部转换:不改变当前状态,仅执行动作(不触发 exit/entry)
  • 自转换:源状态和目标状态相同,但会触发 exit/entry
  • 延迟转换:在状态中停留指定时间后自动触发

4.4 动作(Action)

动作是状态转换时的副作用,也称为效果(Effect)。可以在以下时机触发:

时机 描述 常见用途
entry 进入状态时 初始化、启动定时器
exit 退出状态时 清理、停止定时器
transition 转换发生时 日志、网络请求
do 状态中持续执行 动画、轮询

4.5 守卫条件(Guard)

守卫条件是一个布尔函数,决定转换是否被允许执行:

1
2
3
4
5
// 只有余额充足时才允许转换到"购买成功"
{
target: 'purchaseSuccess',
guard: ({ context }) => context.balance >= context.price
}

守卫条件使得同一个事件在不同条件下可以导向不同的目标状态,增强了表达的灵活性。

4.6 上下文(Context)

上下文(也称扩展状态 Extended State)是与状态机关联的可变数据:

1
2
3
4
5
6
7
8
const machine = createMachine({
context: {
retries: 0,
maxRetries: 3,
lastError: null,
},
// ...
});

上下文弥补了纯 FSM 的一个局限——有限状态无法表达无限的数值域。通过引入上下文,我们可以用有限的状态来表达无限的数据变化。

5 设计模式与实践

5.1 状态设计原则

设计状态时,遵循以下原则可以避免常见的陷阱:

原则一:状态应代表"是什么",而非"做了什么"

1
2
3
4
5
// ❌ 错误:状态中混入了行为描述
states: ['loading', 'showingError', 'submittingForm']

// ✅ 正确:状态描述系统的客观情况
states: ['idle', 'loading', 'error', 'success']

原则二:用状态图穷举所有可能性

在设计阶段,就应当用状态图列出所有状态和转换。这一过程本身就能发现许多潜在的 bug 和遗漏的场景。

原则三:避免"不可能的状态"

1
2
3
4
5
// ❌ 错误:isLoading 和 error 可能同时为 true
{ isLoading: true, error: '网络错误' }

// ✅ 正确:互斥的状态
state: 'loading' | 'error' | 'success'

5.2 常见设计模式

模式一:单一状态变量

将应用状态建模为一个枚举,而非一组布尔标志:

1
2
3
4
5
// ❌ 布尔标志组合
{ isIdle, isLoading, isSuccess, isError }

// ✅ 单一状态枚举
type State = 'idle' | 'loading' | 'success' | 'error';

模式二:状态 + 上下文

分离"有限状态"和"无限数据":

1
2
3
4
5
6
interface MachineContext {
data: null | Response;
error: null | Error;
retryCount: number;
}
type State = 'idle' | 'loading' | 'success' | 'error';

模式三:层次化建模

复杂流程用嵌套状态管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const appMachine = createMachine({
initial: 'unauthenticated',
states: {
unauthenticated: {
initial: 'login',
states: {
login: {},
register: {},
forgotPassword: {},
}
},
authenticated: {
initial: 'dashboard',
states: {
dashboard: {},
settings: {},
profile: {},
}
}
}
});

5.3 测试策略

事件驱动状态机的确定性使得测试非常直观:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 测试状态转换
const actor = createActor(loginMachine);
actor.start();

actor.send({ type: 'submit', username: 'test', password: '123' });
expect(actor.getSnapshot().value).toBe('loading');

// 测试守卫条件
actor.send({ type: 'success', data: mockData });
expect(actor.getSnapshot().value).toBe('authenticated');

// 测试不应发生的转换
actor.send({ type: 'submit' }); // 已认证状态不应接受 submit
expect(actor.getSnapshot().value).toBe('authenticated'); // 状态不变

6 主流框架对比

6.1 框架概览

框架 语言 特点 适用场景
XState TypeScript/JS Statecharts 完整实现、可视化、Actor 模型 前端应用、全栈
transitions Python 轻量、OO 友好、支持层次化 数据处理、后端
Spring StateMachine Java 企业级、与 Spring 生态集成 微服务、工作流
QP/QP-nano C/C++ 嵌入式优化、Active Object、零依赖 实时系统、嵌入式
Akka FSM Scala/Java Actor 模型、分布式 分布式后端
Robot Framework 通用 测试自动化中的状态管理 自动化测试
SMC (State Machine Compiler) 多语言 从状态图定义生成代码 跨平台项目

6.2 XState(TypeScript)

XState 是目前最成熟的 Statecharts 实现之一,由 David Khourshid(@davidkpiano)创建,当前版本为 v5。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { createMachine, assign, createActor } from 'xstate';

const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: {
data: null,
error: null,
retries: 0,
},
states: {
idle: {
on: {
FETCH: { target: 'loading' }
}
},
loading: {
entry: assign({ retries: ({ context }) => context.retries + 1 }),
invoke: {
src: 'fetchData',
onSuccess: { target: 'success', actions: assign({ data: ({ event }) => event.data }) },
onError: [
{ guard: ({ context }) => context.retries < 3, target: 'loading' },
{ target: 'failure', actions: assign({ error: ({ event }) => event.error }) }
]
}
},
success: {
on: {
FETCH: { target: 'loading' }
}
},
failure: {
on: {
RETRY: { target: 'loading' }
}
}
}
});

XState 的核心优势:

  • 可视化:Stately Studio 提供交互式编辑器
  • 类型安全:完整的 TypeScript 类型推导
  • Actor 模型:支持 spawn、消息传递、系统级管理
  • 测试友好:内置模型测试(Model-based Testing)

6.3 transitions(Python)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from transitions import Machine

class Task:
states = ['todo', 'in_progress', 'review', 'done', 'failed']

def __init__(self):
self.machine = Machine(
model=self,
states=Task.states,
initial='todo'
)
self.machine.add_transition('start', 'todo', 'in_progress')
self.machine.add_transition('submit', 'in_progress', 'review')
self.machine.add_transition('approve', 'review', 'done')
self.machine.add_transition('reject', 'review', 'in_progress')
self.machine.add_transition('fail', ['in_progress', 'review'], 'failed')
self.machine.add_transition('reset', 'failed', 'todo')

task = Task()
task.start() # todo -> in_progress
task.submit() # in_progress -> review
task.reject() # review -> in_progress

transitions 支持 HSM(层次化状态机)、Graphviz 图形生成、异步扩展等。

6.4 QP(C/C++)

QP(Quantum Platform)是嵌入式领域最知名的事件驱动框架,由 Miro Samek 创建。它直接实现了 Active Object 模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 定义事件
typedef struct {
QEvt super; // 基类事件
uint16_t key; // 键码
} KeyboardEvt;

// 定义 Active Object
typedef struct {
QActive super; // 基类
uint8_t volume;
} PlayerAO;

// 状态处理函数
QState Player_initial(PlayerAO *me, void const *par) {
return Q_TRAN(&Player_idle);
}

QState Player_idle(PlayerAO *me, QEvt const *e) {
switch (e->sig) {
case PLAY_SIG:
return Q_TRAN(&Player_playing);
case OFF_SIG:
return Q_TRAN(&Player_off);
}
return Q_SUPER(&QHsm_top);
}

QState Player_playing(PlayerAO *me, QEvt const *e) {
switch (e->sig) {
case STOP_SIG:
return Q_TRAN(&Player_idle);
case KEY_PRESS_SIG: {
KeyboardEvt *ke = (KeyboardEvt *)e;
me->volume += ke->key;
return Q_HANDLED();
}
}
return Q_SUPER(&QHsm_top);
}

QP 的特色在于:

  • 零依赖:不依赖操作系统,可在裸机上运行
  • 确定性:适用于硬实时系统
  • 极小 footprint:QP-nano 可以在 8 位 MCU 上运行
  • 内置层次化状态机:支持 Statecharts 的核心特性

7 应用场景深度分析

7.1 前端应用状态管理

现代前端应用中,状态管理是最棘手的问题之一。传统的 “boolean flag” 模式容易产生 不可能状态

1
2
// 灾难:两个标志同时为 true
{ isLoading: true, isError: true, isSuccess: true }

使用状态机可以彻底消除这类问题,因为状态是互斥的。XState 与 React 的结合尤为流行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { useMachine } from '@xstate/react';

function LoginComponent() {
const [state, send] = useMachine(loginMachine);

return (
<div>
{state.matches('idle') && <LoginForm onSubmit={() => send({ type: 'SUBMIT' })} />}
{state.matches('loading') && <Spinner />}
{state.matches('success') && <Navigate to="/dashboard" />}
{state.matches('error') && (
<ErrorMessage
error={state.context.error}
onRetry={() => send({ type: 'RETRY' })}
/>
)}
</div>
);
}

7.2 订单与工作流系统

电商订单是一个经典的状态机场景:

1
2
3
4
5
待支付 ──支付成功──► 待发货 ──发货──► 已发货 ──确认收货──► 已完成
│ │ │
│ 超时 │ 取消 │ 退货
▼ ▼ ▼
已取消 已取消 退货中 ──退款──► 已退款

在企业级场景中,Spring StateMachine 结合持久化存储,可以实现:

  • 状态持久化(数据库存储当前状态)
  • 分布式锁(防止并发状态冲突)
  • 事件溯源(记录所有状态变更历史)

7.3 游戏开发

游戏中的角色 AI 是状态机的经典应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const enemyAI = createMachine({
initial: 'patrol',
context: { playerDistance: Infinity, health: 100 },
states: {
patrol: {
on: { PLAYER_DETECTED: 'chase' }
},
chase: {
on: {
IN_ATTACK_RANGE: 'attack',
PLAYER_LOST: 'patrol',
LOW_HEALTH: 'flee'
}
},
attack: {
on: {
TARGET_DEAD: 'patrol',
OUT_OF_RANGE: 'chase',
LOW_HEALTH: 'flee'
}
},
flee: {
on: {
SAFE: 'patrol',
DEAD: 'dead'
}
},
dead: { type: 'final' }
}
});

更复杂的 AI 会使用层次化状态:attack 状态内部可能包含 meleerangedspecial 等子状态。

7.4 嵌入式与 IoT

在嵌入式系统中,事件驱动的状态机几乎是唯一的架构选择:

  • 资源约束:无法承受传统 RTOS 的开销
  • 实时性要求:必须保证确定性响应时间
  • 事件来源多样:传感器、定时器、通信协议、用户输入

一个智能灯泡的简化状态机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 状态:关灯、开灯白光、开灯暖光、配网中、OTA升级
// 事件:按键、MQTT消息、定时器、传感器
QState Bulb_onWhite(Bulb *me, QEvt const *e) {
switch (e->sig) {
case BUTTON_PRESS_SIG:
return Q_TRAN(&Bulb_warm);
case MQTT_CMD_SIG:
// 处理远程控制命令
handleMqttCmd(me, (MqttEvt *)e);
return Q_HANDLED();
case TIMEOUT_SIG:
return Q_TRAN(&Bulb_off);
}
return Q_SUPER(&Bulb_on);
}

7.5 网络协议解析

网络协议天然适合状态机建模。TCP 连接的状态机是教科书级别的案例:

1
2
3
4
5
CLOSED ──SYN──► SYN_SENT ──SYN+ACK──► ESTABLISHED

│ FIN

FIN_WAIT ──ACK──► TIME_WAIT ──超时──► CLOSED

HTTP 请求处理、WebSocket 连接管理、MQTT 消息解析等都可以用状态机优雅实现。

8 性能与权衡

8.1 性能特征

事件驱动状态机的性能特征:

方面 特征
状态查找 O(1) — 当前状态直接索引
事件分发 O(k) — k 为当前状态的可选转换数
内存占用 极小 — 仅存储当前状态 + 上下文
吞吐量 高 — 无锁、无阻塞
延迟 低 — 事件循环即时处理

8.2 何时使用状态机

适合使用状态机的场景:

  • 状态数量有限且明确
  • 状态转换规则复杂但有规律
  • 需要避免"不可能状态"
  • 需要可视化和文档化业务流程
  • 对可测试性要求高

不适合使用状态机的场景:

  • 状态数量极多或不确定
  • 状态转换完全随机
  • 简单的 CRUD 应用
  • 团队不熟悉状态机范式

8.3 常见反模式

反模式一:上帝状态机

将整个应用逻辑塞进一个状态机,导致状态爆炸。正确做法是将系统分解为多个小型、独立的状态机。

反模式二:滥用全局事件

将所有事件都设为全局可见,导致隐式耦合。正确做法是限定事件的可见范围,使用 Actor 模型进行显式通信。

反模式三:在守卫条件中执行副作用

守卫条件应该是纯函数,不应该修改状态或触发 I/O。

9 从零实现一个简易框架

为了加深理解,让我们用 TypeScript 实现一个最小化的事件驱动状态机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
type State = string;
type Event = { type: string; [key: string]: any };

interface TransitionConfig {
target: State;
guard?: (ctx: any, event: Event) => boolean;
actions?: Array<(ctx: any, event: Event) => any>;
}

interface StateConfig {
on?: Record<string, TransitionConfig | TransitionConfig[]>;
entry?: Array<(ctx: any, event: Event) => any>;
exit?: Array<(ctx: any, event: Event) => any>;
}

interface MachineConfig {
initial: State;
context: Record<string, any>;
states: Record<string, StateConfig>;
}

class StateMachine {
private current: State;
private context: Record<string, any>;
private config: MachineConfig;
private listeners: Array<(state: State, context: any) => void> = [];

constructor(config: MachineConfig) {
this.config = config;
this.current = config.initial;
this.context = { ...config.context };
}

get state() { return this.current; }
get ctx() { return this.context; }

subscribe(listener: (state: State, context: any) => void) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}

send(event: Event): void {
const stateConfig = this.config.states[this.current];
if (!stateConfig?.on?.[event.type]) return;

const transitions = Array.isArray(stateConfig.on[event.type])
? stateConfig.on[event.type]
: [stateConfig.on[event.type]];

for (const transition of transitions) {
if (transition.guard && !transition.guard(this.context, event)) continue;

// 执行 exit 动作
stateConfig.exit?.forEach(action => action(this.context, event));

// 执行转换动作
transition.actions?.forEach(action => action(this.context, event));

// 切换状态
const prevState = this.current;
this.current = transition.target;

// 执行 entry 动作
const newStateConfig = this.config.states[this.current];
newStateConfig?.entry?.forEach(action => action(this.context, event));

// 通知监听者
this.listeners.forEach(l => l(this.current, this.context));

console.log(`[${prevState}] --${event.type}--> [${this.current}]`);
return; // 只执行第一个匹配的转换
}
}
}

// 使用示例
const trafficLight = new StateMachine({
initial: 'red',
context: { cycleCount: 0 },
states: {
red: {
entry: [(ctx) => console.log('🚗 停车!')],
on: {
TIMER: { target: 'green' }
}
},
green: {
entry: [(ctx) => console.log('🚗 通行!')],
on: {
TIMER: { target: 'yellow' }
}
},
yellow: {
entry: [(ctx, e) => { ctx.cycleCount++ }],
on: {
TIMER: {
target: 'red',
guard: (ctx) => ctx.cycleCount < 5,
// 循环 5 次后...(此处无匹配则停留在 yellow)
}
}
}
}
});

trafficLight.send({ type: 'TIMER' }); // red -> green
trafficLight.send({ type: 'TIMER' }); // green -> yellow
trafficLight.send({ type: 'TIMER' }); // yellow -> red
// ...循环往复

这个简易实现涵盖了状态机的核心要素:状态、事件、转换、守卫条件、动作和上下文。生产级框架在此基础上还会增加层次化状态、并行状态、延迟转换、Actor 模型等高级特性。

10 总结

事件驱动的状态机框架是一种经过时间考验的架构模式,其核心优势在于:

  1. 确定性:相同输入总是产生相同输出,便于测试和验证
  2. 可视化:状态图天然是文档,降低沟通成本
  3. 防御性编程:通过消除"不可能状态"减少 bug
  4. 可扩展性:从简单 FSM 到 Statecharts,可以渐进式增加复杂度
  5. 跨领域通用:从前端到嵌入式,从游戏到网络协议,同一套思维模型

无论你选择 XState、transitions、QP 还是自研框架,理解其背后的核心原理——有限状态、事件驱动、确定性转换——都能帮助你构建更健壮、更可维护的软件系统。

参考资料



文章链接:
https://www.zywvvd.com/notes/coding/design-pattern/event-driven-state-machine/event-driven-state-machine/


“觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭”

微信二维码

微信支付

支付宝二维码

支付宝支付

事件驱动的状态机框架:从理论到工程实践
https://www.zywvvd.com/notes/coding/design-pattern/event-driven-state-machine/event-driven-state-machine/
作者
Yiwei Zhang
发布于
2026年4月21日
许可协议