什么是状态机?
有限状态机,英文翻译是 Finite State Machine,缩写为 FSM,简称为状态机。状态机有 3 个组成部分:状态(State)、事件(Event)、动作(Action)。其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。
实现状态机的方法有多种,比较常用的有分支逻辑法、查表法、状态模式。
我们以一个简单的 CD 播放器为例子。这个例子里面只有状态、事件,不包含动作
简单CD播放器的按键与按键的功能
按键 | 功能 |
[Play/Pause] | 播放/暂停 |
[Stop] | 停止 |
状态迁移图:
状态机实现方式一:分支逻辑法
它的核心思想是根据状态迁移图,要么先确定状态、要么先确定事件,直译代码。
方法分析:对于简单状态机,该法是可以接受的。但是,对于复杂的状态机,这种实现极易漏写或错写某个状态转移;代码中充斥大量if-else或switch-case 分支判断逻辑,可读性和可维护性差。
如下就是先确定事件,然后再在事件内根据状态进行状态转移。
1 typedef enum {
2 ST_IDLE,
3 ST_PLAY,
4 ST_PAUSE
5 } State;
6
7 typedef enum {
8 EV_PLAY_PAUSE,
9 EV_STOP
10 } Event;
11
12 State state;
13
14 // 初始化
15 void initialize() {
16 state = ST_IDLE;
17 }
18
19 // play or pause
20 void playOrPause() {
21 if (state == ST_IDLE) {
22 state = ST_PLAY;
23 } else if (state == ST_PLAY) {
24 state = ST_PAUSE;
25 } else if (state == ST_PAUSE) {
26 state = ST_PLAY;
27 }
28 }
29
30 // stop
31 void stop() {
32 if (state == ST_PLAY || state == ST_PAUSE) {
33 state = ST_IDLE;
34 }
35 }
36
37 // 事件响应
38 void onEvent(Event ev) {
39 switch (ev) {
40 case EV_PLAY_PAUSE:
41 playOrPause();
42 break;
43 case EV_STOP:
44 stop();
45 break;
46 default:
47 break;
48 }
49 }
状态机实现方法二:查表法
状态机除了用状态转移图表示外,还可以用二维表表示。如下第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态即执行的动作。
CD播放器状态迁移表
状态/事件 | 停止(EV_STOP) | 播放/暂停(EV_PLAY_PAUSE) |
空闲(ST_IDLE) | 忽略 | 开始播放并转为播放状态 |
播放(ST_PLAY) | 停止并转为空闲状态 | 停止并转为暂停状态 |
暂停(ST_PAUSE) | 停止并转为空闲状态 | 播放并转为播放状态 |
相比于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,只需要修改transitionTable和actionTable两个二维数组即可。
1 typedef enum {
2 ST_IDLE,
3 ST_PLAY,
4 ST_PAUSE
5 } State;
6
7 typedef enum {
8 EV_STOP,
9 EV_PLAY_PAUSE
10 } Event;
11
12 State state;
13
14 // transitionTable[i][j]表示状态为i时,发生事件j后,状态迁移到transitionTable[i][j]
15 State transitionTable[][2] = {
16 {ST_IDLE, ST_PLAY},
17 {ST_IDLE, ST_PAUSE},
18 {ST_IDLE, ST_PLAY}
19 };
20
21 // 初始化
22 void initialize() {
23 state = ST_IDLE;
24 }
25
26 State executeEvent(Event ev) {
27 return transitionTable[state][ev];
28 }
29
30 // play or pause
31 void playOrPause() {
32 state = transitionTable[state][EV_PLAY_PAUSE];
33 }
34
35 // stop
36 void stop() {
37 state = transitionTable[state][EV_STOP];
38 }
39
40 // 事件响应
41 void onEvent(Event ev) {
42 switch (ev) {
43 case EV_PLAY_PAUSE:
44 playOrPause();
45 break;
46 case EV_STOP:
47 stop();
48 break;
49 default:
50 break;
51 }
52 }
53
54 int main(void) {
55 initialize();
56 onEvent(EV_PLAY_PAUSE); // 播放
57 printf("%d ", state);
58 onEvent(EV_PLAY_PAUSE); // 暂停
59 printf("%d ", state);
60 onEvent(EV_STOP); // 停止
61 printf("%d\n", state);
62 }
执行结果为:1 2 0
状态机实现方式三:状态模式
事件触发的动作如果非常简单,可以用二维的actionTable表示,则可以使用查表法。但是,如果执行的动作是一系列复杂的逻辑操作(比如加减积分、写数据库、发送消息等),就没有办法用简单的二维数组来表示了。