状态机、状态模式

什么是状态机?

有限状态机,英文翻译是 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
", state);
62 }

执行结果为:1  2  0

状态机实现方式三:状态模式

事件触发的动作如果非常简单,可以用二维的actionTable表示,则可以使用查表法。但是,如果执行的动作是一系列复杂的逻辑操作(比如加减积分、写数据库、发送消息等),就没有办法用简单的二维数组来表示了。

参考资料

1.64 | 状态模式:游戏、工作流引擎中常用的状态机是如何实现的? (geekbang.org)

2.《C现代编程  集成开发环境、设计模式、极限编程、测试驱动开发、重构、持续集成》

原文地址:https://www.cnblogs.com/yanxin880526/p/15115761.html