狀態機、狀態模式


什么是狀態機?

有限狀態機,英文翻譯是 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表示,則可以使用查表法。但是,如果執行的動作是一系列復雜的邏輯操作(比如加減積分、寫數據庫、發送消息等),就沒有辦法用簡單的二維數組來表示了。

 

參考資料

1.64 | 狀態模式:游戲、工作流引擎中常用的狀態機是如何實現的? (geekbang.org)

2.《C現代編程  集成開發環境、設計模式、極限編程、測試驅動開發、重構、持續集成》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM