iT邦幫忙

2021 iThome 鐵人賽

DAY 9
0
Software Development

From State Machine to XState系列 第 9

Day09 - 實作一個狀態機 - 2

  • 分享至 

  • xImage
  •  
  1. State Machine 不完備,沒有一個變數能幫我記憶當下的狀態是什麼,我在使用 transition 時還要另外用一堆變數來儲存 state ,感覺不好維護、也不好維持程式碼的連貫性
  2. 當 state 跟 event 都是直接使用字串,很容易打錯、出現 bug ,所以想要用常數保護起來
  3. 用雙層 switch case 好像比較不好讀,感覺也不太好重複使用

今天先來針對第 3 點改進,我們用到很多 switch case,回顧一下狀態機的定義,一個狀態機描述一個實體、對象,根據這點,我們能不能據此做點抽象化呢?既然說狀態機有個對象,如果我們嘗試著直接用 object 來描述看看呢?

首先一樣要有個初始狀態

const machineDef = {
  initialState: "站姿、靜止",  // 初始狀態
};

接著還有狀態機底下的所有狀態

const machineDef = {
  initialState: "站姿、靜止",  // 初始狀態
  // 所有狀態
  states: {
    "站姿、靜止": ?,
    "站姿、移動": ?,
    "跳躍中": ?,
    "俯臥、靜止": ?,
    "俯臥、移動": ?,
  },
};

接著狀態之後該放入什麼東西呢?我們知道至少還有個轉移,nextState = transition(previusState, event),轉移是需要狀態、事件一起作為輸入。

所以上面的 ? 是否可以放入一個 mapping 來存放 eventnextState 的關係。

const machineDef = {
  initialState: "...",  // 初始狀態
  // 所有狀態
  states: {
    "previusState1": mapping1,
    "previusState2": mapping2,
    "previusState3": mapping3,
  },
};

一般 mapping 的實作常見可以使用 function (如果放在物件的會就是 method),然後該 function 裡面可以再用 switch/caseif/else 實作,但...回頭來看今天的初衷,就是要避免 switch/case

所幸我們想到,mapping 也可以透過 object literal 替代

所以我們就決定採用 object literal 來製作 eventnextState 的對應關係(mapping)吧!

「事件」這兩個字,讓我們回想 JavaScript 如何處理、對待事件,以按鈕 button 為例,<button onclick="">點我啊~</button> ,我們會對處於被動、等待、監聽的狀態時,通常會使用 on 這個前綴字 (如同 onClick, onSubmit, onFocus...)

那就嘗試在每個 state 下,建立一個名為 on 的 object literal ,裡面裝有所有的 eventnextState 的關係(即 transition mapping),對應關係的 key / value 怎麼配? previusState 要配 event ,既然前面已經有 previusState1 再加上我們又使用 on 這個前綴字,想必這個 mapping 的 key 必定是使用 event ,而 value 就是剩下來的 nextState

我們知道

const machineDef = {
  initialState: "...",  // 初始狀態
  // 所有狀態
  states: {
    "previusState1": on:{ 
      "event1": "nextState1",
      "event2": "nextState2",
    },
  },
};

接著要回來繼續實作了,為了簡單看到 previusState, eventnextState ,彼此之間的關係,我們再度呼叫 State Diagram 出來~~~

https://ithelp.ithome.com.tw/upload/images/20210921/20130721Kh6DzCoDyK.png

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!


上一篇
Day08 - 實作一個狀態機 - 1
下一篇
Day10 - 實作一個狀態機 - 3
系列文
From State Machine to XState31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
TD
iT邦新手 4 級 ‧ 2021-09-25 09:13:52

假如有更多種狀態和動作的話,machineDef 可能就會變得非常龐大,有什麼方法可以拆分 machineDef 的嗎?

Ken Chen iT邦新手 4 級 ‧ 2021-09-26 21:24:53 檢舉

不好意思這麼晚回覆,我自己目前也在研究的階段
以現階段我可以提出的一些觀點如下:

  1. 思考這個狀態機是否就是如此複雜?有沒有分散邏輯、切細的可能

狀態機開發也是可以有 Atom 的概念,像我前面舉 RPG 的例子,我的遊戲「角色」,是只有移動模組,還可以另外創造攻擊模組、防禦模組這類的狀態機,一起組合成這個遊戲角色整體的狀態。

這個相關的概念也會在幾天後的篇幅被介紹到

  1. 不能拆細、如此龐大的話,我們是否能透過模組化的概念,使程式碼關注點分離,將這個 config 儲存於特定的模組,在使用狀態機時,引入這組設定。
TD iT邦新手 4 級 ‧ 2021-09-27 08:40:19 檢舉

Ken Chen謝謝回覆!期待後續的介紹 :)

我要留言

立即登入留言