iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
Software Development

From State Machine to XState系列 第 8

Day08 - 實作一個狀態機 - 1

根據需求畫出狀態圖後
https://ithelp.ithome.com.tw/upload/images/20210921/20130721Kh6DzCoDyK.png

  1. 他們都有一個初始狀態
  2. 整個實體、物件或是對象,都有一系列不同的狀態們
  3. 還有一系列不同的事件,可能會導致狀態轉移
  4. 還有一張紀錄著什麼事件配什麼狀態會發生轉移的地圖、對應表
  5. 有限數量的結束狀態( 0 - n 個 )

接著我們便來思考如何實作,
(1). 有初始狀態:所以我們需要有個變數( state )來儲存狀態,而且這個狀態的初始值就是初始狀態
(2). 一系列的狀態們:狀態改變時,也應該被儲存起來( state )
(3). 事件、轉移:當某個狀態 + 某個事件會導致狀態的轉移,nextState = transition(previusState, event),所以要實作一個 function transition,這個 function 裡應該會有個 mapping ,記錄著什麼狀態 + 什麼事件可以轉移狀態。

(1). 初始狀態存入一個變數

const initialState = "站姿、靜止"

(3). 事件、轉移,實作一個 function transition

關於這個 transition ,首先會想到 input 進去,為了辨識什麼 state, event 來決定要回傳什麼,執行辨識的底層實作一定會經歷許多邏輯判斷,才會回傳對應的結果給我們。假設狀態會有許多個,如果不想大量使用 if/else ,此時我們想到可以使用 switch/case

const initialState = "站姿、靜止"
// 3 實作 transition
function transition (state) {
  switch(state){
    case "站姿、靜止": // 3.1 - 過濾狀態
      // 3.2 - 回傳經過轉移後的狀態 (回傳 nextState )
      return "站姿、移動"
    case "站姿、移動":
      return "站姿、靜止"
    case "跳躍中":
      return "站姿、移動"
    case "俯臥、靜止":
    case "俯臥、移動":

    default:
      throw "The state is not existed, machine broken"
  }
}

上面的實作中,我們已經能在第一個 switch case 辨識 state ,那促使 state 轉移的是什麼呢? 是事件 event,而 transition 則是根據對應的 state 及對應的 event 發生時,改變 state,所以我們接著需要在 state 底下來辨識 event ,才能達到我們說的 某個狀態 + 某個事件會導致狀態的轉移,nextState = transition(previusState, event)

function transition (state,event) {
  switch(state){
    case "站姿、靜止": 
      switch(event){
        case "跳躍":
           // 3.2 - 回傳經過轉移後的狀態 (回傳 nextState )
           return  "跳躍中"
        case "開始移動":
           return  "站姿、移動"
        case "臥倒":
           return  "俯臥、靜止"
        default:
           // 如果 "站姿、靜止" 狀態,遇到 "跳躍","開始移動","臥倒" 以外的事件
           // 不轉移狀態
           return state
      }
    case "站姿、移動":
      switch(event){
        case "停止移動":
           return  "站姿、靜止"
        default:
           // 如果 "站姿、移動" 狀態,遇到 "停止移動" 以外的事件
           // 不轉移狀態
           return state
      }
    case "跳躍中":
      switch(event){
        case "降落":
           return  "站姿、靜止"
        default:
           // 如果 "跳躍中" 狀態,遇到 "降落" 以外的事件
           // 不轉移狀態
           return state
      }
    case "俯臥,靜止": // 應該是 "俯臥、靜止" 沒用常數保護的話,打錯就出現 bug 了
      switch(event){
        case "起身":
           return  "站姿、靜止"
        case "開始移動":
           return  "俯臥、移動"
        default:
           // 如果 "俯臥、靜止" 狀態,遇到 "起身", "開始移動" 以外的事件
           // 不轉移狀態
           return state
      }
    case "俯臥、移動":
      switch(event){
        case "停止移動":
           return  "俯臥、靜止"
        default:
           // 如果 "俯臥、移動" 狀態,遇到 "停止移動” 以外的事件
           // 不轉移狀態
           return state
      }
    default:
      throw "The state is not existed, machine broken"
  }
}

(2). 一系列的狀態們:狀態改變時,也應該被儲存起來( state )


const initialState = "站姿、靜止"

function transition (state,event) {
  // ...
}

const nextState = transition(initialState,"")  // "站姿、靜止"
const nextState2 = transition(initialState,"開始移動")  // "站姿、移動"
const nextState3 = transition(nextState2,"停止移動")  // "站姿、靜止"
const nextState4 = transition(nextState3,"臥倒")  // "臥倒、靜止"

如此我們便可以根據對應的 state 及 event 來決定要不要進行 transition 了,也算是初步完成我們的 state machine 了。我們來回顧一下現在還缺什麼

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

明天讓我們根據以上不足之處,繼續完備一個 State Machine

參考文獻


上一篇
Day07 - Flowchart versus State Diagram 讓我們比一比
下一篇
Day09 - 實作一個狀態機 - 2
系列文
From State Machine to XState31

1 則留言

0
TD
iT邦新手 5 級 ‧ 2021-09-23 17:04:43

終於看到傳說中的狀態機了!

我要留言

立即登入留言