今天先來針對第 3 點改進,我們用到很多 switch case,回顧一下狀態機的定義,一個狀態機描述一個實體、對象,根據這點,我們能不能據此做點抽象化呢?既然說狀態機有個對象,如果我們嘗試著直接用 object 來描述看看呢?
首先一樣要有個初始狀態
const machineDef = {
initialState: "站姿、靜止", // 初始狀態
};
接著還有狀態機底下的所有狀態
const machineDef = {
initialState: "站姿、靜止", // 初始狀態
// 所有狀態
states: {
"站姿、靜止": ?,
"站姿、移動": ?,
"跳躍中": ?,
"俯臥、靜止": ?,
"俯臥、移動": ?,
},
};
接著狀態之後該放入什麼東西呢?我們知道至少還有個轉移,nextState = transition(previusState, event)
,轉移是需要狀態、事件一起作為輸入。
所以上面的 ? 是否可以放入一個 mapping 來存放 event
跟 nextState
的關係。
const machineDef = {
initialState: "...", // 初始狀態
// 所有狀態
states: {
"previusState1": mapping1,
"previusState2": mapping2,
"previusState3": mapping3,
},
};
一般 mapping 的實作常見可以使用 function (如果放在物件的會就是 method),然後該 function 裡面可以再用 switch/case
或 if/else
實作,但...回頭來看今天的初衷,就是要避免 switch/case
。
所幸我們想到,mapping 也可以透過 object literal 替代
所以我們就決定採用 object literal 來製作 event
跟 nextState
的對應關係(mapping)吧!
「事件」這兩個字,讓我們回想 JavaScript 如何處理、對待事件,以按鈕 button 為例,<button onclick="">點我啊~</button>
,我們會對處於被動、等待、監聽的狀態時,通常會使用 on 這個前綴字 (如同 onClick, onSubmit, onFocus...)
那就嘗試在每個 state 下,建立一個名為 on 的 object literal ,裡面裝有所有的 event
跟 nextState
的關係(即 transition mapping),對應關係的 key / value 怎麼配? previusState
要配 event
,既然前面已經有 previusState1
再加上我們又使用 on 這個前綴字,想必這個 mapping 的 key 必定是使用 event
,而 value 就是剩下來的 nextState
我們知道
const machineDef = {
initialState: "...", // 初始狀態
// 所有狀態
states: {
"previusState1": on:{
"event1": "nextState1",
"event2": "nextState2",
},
},
};
接著要回來繼續實作了,為了簡單看到 previusState
, event
跟 nextState
,彼此之間的關係,我們再度呼叫 State Diagram 出來~~~
const machineDef = {
initialState: "站姿、靜止",
states: {
"站姿、靜止": {
on: {
跳躍: "跳躍中",
開始移動: "站姿、移動",
臥倒: "俯臥、靜止",
},
},
"站姿、移動": {
on: {
停止移動: "站姿、靜止",
},
},
跳躍中: {
on: {
降落: "站姿、靜止",
},
},
"俯臥、靜止": {
on: {
起身: "站姿、靜止",
開始移動: "俯臥、移動",
},
},
"俯臥、移動": {
on: {
停止移動: "俯臥、靜止",
},
},
},
};
如此可讀性是不是看起來比較提升呢?也大幅減少程式碼行數,省掉不必要的 switch / case
那今天針對第三點做了改進
3. 用雙層 switch case 好像比較不好讀,感覺也不太好重複使用
這個 object 看起來就像是 state Machine 的定義一般,那我們可以怎麼操縱這個物件呢?
關於第二點
2. 當 state 跟 event 都是直接使用字串,很容易打錯、出現 bug ,所以想要用常數保護起來
我們可以把 object 的 key 另外再用 const
儲存成常數,或是使用 TypeScript 的 enum
剩下第一點,還有第三點當中的重複使用(這裡還沒多做解釋)
1. State Machine 不完備,沒有一個變數能幫我記憶當下的狀態是什麼,我在使用 transition 時還要另外用一堆變數來儲存 state ,感覺不好維護、也不好維持程式碼的連貫性
我們可以製作一個 function 然後將 machineDef
這個物件 作為 input,明天就來跟大家分享怎麼建立這個 function!
假如有更多種狀態和動作的話,machineDef
可能就會變得非常龐大,有什麼方法可以拆分 machineDef
的嗎?
不好意思這麼晚回覆,我自己目前也在研究的階段
以現階段我可以提出的一些觀點如下:
狀態機開發也是可以有 Atom 的概念,像我前面舉 RPG 的例子,我的遊戲「角色」,是只有移動模組,還可以另外創造攻擊模組、防禦模組這類的狀態機,一起組合成這個遊戲角色整體的狀態。
這個相關的概念也會在幾天後的篇幅被介紹到
Ken Chen謝謝回覆!期待後續的介紹 :)