從任務中心看狀態機功能元件設計

阿里巴巴TXD發表於2019-03-02

從任務中心看狀態機功能元件設計

本文通過設計一個簡單的任務中心來展示一個使用有限狀態機思想設計該如何做。

本文使用 JSDOC 新增註釋。

關於有限狀態機

有限狀態機 (FSM) 維基 百度百科 是用來表示有限個狀態及在這些狀態之間轉移和動作的數學模型。早在07年就有文章講解 js 與狀態機的結合。

在程式中,我們用這種數學模型抽象業務的複雜行為。作為狀態機,有以下特徵特點:

  1. 事物擁有的狀態是有限的。
  2. 事物在任一時刻,只處在一種狀態。
  3. 可以通過觸發事物的某些行為,可以導致事物從一種狀態過渡到另外一種狀態。
  4. 一種行為一般只能將其他狀態變為一種特定狀態,但不能將其變為多種狀態。

設計實現元件

任務中心具有以下功能:

  • 任務中心可以在任何時刻加入新任務
  • 任務中心在啟動時才會執行任務
  • 任務在任務中心執行時會被即時執行
  • 任務中心可以被終止,暫停,恢復

如果針對以上需求,整理整個流程, 可以認為任務中心的流程如下:

從任務中心看狀態機功能元件設計

因為需求中存在“可以在任何時間塞入任務”,所以對於不同的流程設定任務的不同執行狀態。

  1. 任務中心建立後即可允許塞入任務
  2. 任務中心執行狀態中,任何任務塞入會被即時執行
  3. 任務中心暫停或停止時,任何任務塞入也不會被執行
  4. 任務中心恢復執行時,會執行所有之前塞入並沒有被執行的任務

分離了任務中心和任務,這兩種並行的狀態,我們就可以分開設計,分開編碼了。

那麼先做任務中心元件的實際設計,關於這個元件可能有的狀態如下:

  1. 初始狀態

  2. 執行狀態

  3. 暫停狀態

  4. 恢復狀態

  5. 恢復後的執行狀態

  6. 停止狀態

  7. 重啟狀態

........

但由於我們元件有的功能,我們僅為元件設計了四個狀態就可以描述其整個生命流程:

  1. IDLE 初始化狀態
  2. RUNNING 執行狀態
  3. PAUSED 暫停狀態
  4. STOPPED 停止狀態

因為我們的這個任務中心是給任務排程用的,所以僅跟任務有關的狀態才是有用的狀態,這也是狀態機設計思維的核心之一,僅設計使用相關聯的狀態, 並保證剩餘的狀態可成為完整的流程, 任務中心的四個狀態組成流程如下圖所示:

從任務中心看狀態機功能元件設計

實現元件

描述狀態

為了更好的展示事務狀態,我們在元件狀態切換中加入明確的狀態標識。並在類的狀態列表定義它們:

/**
 * 列舉 InstantTask 元件狀態
 * @enum {Number}
 * @readonly
 */
InstantTask.State = Object.freeze({
  /**
   * @memberof InstantTask.State
   * @type {Number}
   */
  IDLE: 0,
  /**
   * @memberof InstantTask.State
   * @type {Number}
   */
  RUNNING: 1,
  /**
   * @memberof InstantTask.State
   * @type {Number}
   */
  PAUSED: 2,
  /**
   * @memberof InstantTask.State
   * @type {Number}
   */
  STOPPED: 3,
});
複製程式碼

描述行為

元件一共存在五個行為, 這些行為在數學中可以被認為是變換器,它們就是上述狀態機運轉圖的具體行為體現。

  1. start()

    用於啟動元件,設定元件為 RUNNING 狀態。

    上一個狀態可以是任何狀態。

    用於啟動任務中心,並會執行所有之前塞入並沒有被執行的任務

  2. pause()

    用於暫停元件,設定元件為 PAUSED 狀態。

    上一個狀態只能是 RUNNING 狀態。

    用於暫停任務中心,在暫停後任何任務塞入也不會被執行

  3. resume()

    用於恢復元件的暫停狀態,設定元件為 RUNNING 狀態

    上一個狀態只能是 PAUSED 狀態

    用於啟動任務中心,並會執行所有之前塞入並沒有被執行的任務,和start()之間功能差距不大,但是不會像start()一樣重置所有設定,而且主要用途是和pause方法做一一對應。一個方法或者狀態的多用途對於狀態機設計來說會增加架構複雜度,儘量避免吧。

  4. stop()

    用於停止任務中心,設定元件為 STOPPED 狀態

    上一個狀態只能是 RUNNING 狀態

    用於停止任務中心,和pause的區別是它會記錄停止狀態,並且不能被resume,和pause內部邏輯差距不大。主用途是和start()做對立方法。

  5. reset()

    用於重置任務中心,設定元件為 IDLE 狀態

    上一個狀態可以是任何狀態, 但主要是 STOPPED 狀態

    用於重置任務中心,此方法等於生成一個新的任務中心。

實現元件

當我們確定元件的抽象執行狀態和執行行為後,就可以結合實際業務邏輯來實現具體的元件了。 本文中使用 EventEmitter 來作為事件的廣播和處理,前端也可以借用 eventemitter3 這樣的近似的類實現同樣的功能。

同時接下來本文使用程式碼中註釋的方式繼續說明每個方法的詳細用途:

具體程式碼已經放在: https://gist.github.com/Suixinlei/4b2da4ee1ef84e89b5cfccc1b88b3e4f

測試程式碼放在: https://gist.github.com/Suixinlei/d8441babe5174b7b1d4326f39b0fcff2

測試結果如下所示,我們可以看到實際執行結果和我們的抽象設計完全一致,充分證明抽象設計對最終邏輯的影響:

新增任務 instant1
新增任務 instant2
任務中心開始執行
instant1 run
instant2 run
新增任務 instant3
instant3 run
任務中心已暫停
新增任務 instant4
恢復執行
instant4 run
複製程式碼

擴充延伸

目前來說,所有任務都是即時任務,所以對於任務中心的狀態僅有四種即可滿足需求。如果這些任務有一些是定時任務,甚至可以迴圈執行並規定執行次數呢?讀完這篇文章我想你心中應該已經有些想法, 那麼看看下面的最終實現和你想的是否有些出入呢?

這裡是最終實現: https://gist.github.com/Suixinlei/e245812799fa160cdf0bbcdf279e68bc

總結

使用 FSM 設計元件可以

  1. 讓元件變得邏輯清晰
  2. 元件邏輯和業務邏輯分離,從概念上有良好的分層結構
  3. 元件易測試,元件實現初期僅需測試元件邏輯部分而非複雜的業務邏輯
  4. 梳理元件邏輯閉環,可以較容易的發現元件生命週期中缺少的部分,反推業務進行改進

從任務中心看狀態機功能元件設計

關注檢視更多原創內容

關注公眾號投遞簡歷 (招聘視覺、互動、前端)

相關文章