前端設計模式用起來(1)狀態模式

業務代碼開發久了,偶爾看看設計模式,總會讓本身有一種清新脫俗的感受。總想把這種感受記下來,但一想到要先起個恰如其分的標題和開頭,就讓我有一種百爪撓心的糾結,因此遲遲沒有開始。今天起更新我學習設計模式筆記的緣由,就好像是,你喜歡一個女孩久了,卻總不表白,難道不怕被別人截胡了麼!

首先咱們來一塊兒設想一些場景:javascript

  • 程序員們在等電梯的時候,聊天頻率最高的一個話題,是否是電梯調度算法呢。寫字樓的幾部電梯究竟是分單雙層運力快,仍是高低層運力快?當你按下電梯時,是就近樓層的電梯視來接你,仍是自顧自的先上後下順帶來接你?
  • 開車來到路口,對紅綠燈亮滅的長短是否曾有過習慣性的吐槽

除了電梯調度、紅綠燈控制,軟件設計和業務開發中,相似諸如狀態切換的問題不難遇到。他們的共同點是:場景存在多個狀態,狀態改變時會觸發對應的不一樣處理方法,狀態間切換又存在諸多約束和限制前端

面對這種場景,你腦海裏的第一解決方案是什麼?條件分支if...else或者switch...case麼?其實應當視具體場景複雜度來看,若是狀態少邏輯簡單,條件分支力所能及。但假若狀態較多,邏輯複雜,又存在諸多特殊狀況的約束限制,本文將介紹的狀態模式歡迎來解一下。java

狀態模式

定義:當一個對象的內在狀態改變時,容許改變其行爲,這個對象看起來像是改變了其類

咱們先來看下傳統面嚮對象語言的類圖,後面再細說前端js中該如何應用
圖片描述
對於一個if...else處理的長流程,咱們能夠抽象成多個狀態的切換,不一樣具體的狀態繼承自一個抽象狀態接口。場景類維護當前狀態對象的一個實例,以及狀態切換間的約束關係。程序員

這樣作的好處是將狀態的獲取和狀態的切換進行了分離,一個具體的狀態類只處理本狀態相關的邏輯,符合單一職責原則,後期若是新加狀態只須要新建具體的狀態類,符合開放封閉原則算法

JavaScript沒有抽象接口類的概念,全部類圖也大大簡化,以下:
狀態模式UML類圖
State類爲狀態類,包含狀態值以及狀態改變時具體的處理方法。Context類爲場景類,維護狀態間的約束關係以及控制觸發狀態的切換。
下面咱們看下代碼,讓概念平穩落地:npm

// 定義狀態類
class State {
    constructor(color) {
        this.color = color
    }
    // 處理該狀態下具體邏輯
    handle(context) {
        console.log(`turn to ${this.color}`)
        context.setState(this)
    }
}
// 定義場景類
class Context {
    constructor() {
        this.state = null
    }
    setState(state) {
        this.state = state
    }
    getState() {
        return this.state
    }
}

測試代碼以下:設計模式

const ctx = new Context()
// 實例出具體狀態
const red = new State('red')
const green = new State('green')
const yellow = new State('yellow')
// 綠燈亮
green.handle(ctx)
console.log(ctx.getState())
// 紅燈亮
red.handle(ctx)
console.log(ctx.getState())
// 黃燈亮
yellow.handle(ctx)
console.log(ctx.getState())

有限狀態機

狀態模式脫胎自有限狀態機(Finite-State-Machine),這個數學模型描述了有限個狀態,以及這些狀態之間轉移和動做的行爲,是一種對象行爲建模的工具。相似下圖就是一種有限狀態機:
圖片描述
其實咱們在處理業務邏輯時,常常打交道的各類事件和狀態切換,寫的各類if...elseswitch...case都是有限狀態機模型,只是平時沒有意識到吧了。在處理較爲複雜的邏輯時,考慮把業務邏輯抽象成一個有限狀態機模型,經常會是代碼邏輯清晰,結構規整。函數

有限狀態機能夠概括出四個要素:工具

  1. 現態:即當前的狀態。
  2. 條件:又稱爲「事件」。當一個條件被知足,將會觸發一個動做,或者執行一次狀態的遷移。
  3. 動做:條件知足後執行的動做。動做執行完畢後,能夠遷移到新的狀態,也能夠仍舊保持原狀態。動做不是必需的,當條件知足後,也能夠不執行任何動做,直接遷移到新狀態。
  4. 次態:條件知足後要遷往的新狀態。次態是相對於現態而言的,次態一旦被激活,就轉變成新的現態了。
Tips:避免把某個程序 動做看成是一種 狀態來處理,動做是不穩定的,即便條件沒有觸發,一旦動做執行完也就結束了;但狀態是穩定的,若是沒有外部條件觸發,狀態會一直持續下去。

介紹了有限狀態機,咱們固然能夠經過上面介紹的狀態模式的方式,來將這種模型工具應用到咱們的代碼開發當中。可是你有沒有注意到一個問題?對,代碼不夠優雅,略顯簡陋,不能忍!學習

接下來介紹一個優雅的有限狀態機實現類庫javascript-state-machine,接下來使用這個類庫簡單實現一個Promise的功能,來看一下如何使用。

Promise簡單實現

首先回顧一下Promise的特色:

  • Promise是一個類。
  • Promise在實例初始化的時候須要傳入一個函數。
  • 傳入的函數須要接收resolvereject兩個函數,成功的時候調用resolve,失敗的時候調用reject
  • Promise實例出的對象有一個then方法,能夠進行鏈式操做。
  • Promise擁有三種狀態:pendingfulfilledrejected,能夠從pending->fulfilled,或pending->rejected,但不能逆向。

接下來上代碼實現一下

// 狀態機模型
const fsm = new StateMachine({
    // 初始狀態
    init: 'pending',
    // 狀態遷移規則,name,from,to的名字儘可能別同名
    transitions: [
        { name: 'resolve', from: 'pending', to: 'fulfilled' },
        { name: 'reject', from: 'pending', to: 'rejected'}
    ],
    methods: {
        onResolve(state, data) {
            data.successFn.forEach(fn => fn())
        },
        onReject(state, data) {
            data.failFn.forEach(fn => fn())
        }
    }
})
// 定義Promise
class MyPromise {
    constructor(fn) {
        this.successFn = []
        this.failFn = []
        fn(
            () => { fsm.resolve(this)},
            () => { fsm.reject(this)}
        )
    }
    then(successFn, failFn) {
        this.successFn.push(successFn)
        this.failFn.push(failFn)
        return this
    }
}

總結

本文介紹了狀態模式和有限狀態機的概念,以及纔開發中優雅使用的姿式javascript-state-machine,並經過用其簡單實現了Promise的基本功能,演示瞭如何使用。

其實重點仍是狀態模式,經過本文介紹能夠很明顯地感覺到其優勢:

  1. 結構清晰,避免了過多的if...elseswitch...case的使用,下降了程序的複雜性,提升了系統的可維護性。
  2. 很好遵循了開放封閉原則和單一職責原則。
記得Martin在《重構》中,提到一個壞的代碼味道「 Long Method」,當你遇到一個方法中包含了一大堆邏輯,作了不少事的時候,你就應該嗅探到一股惡臭味,怎麼去修改,或許考慮使用狀態模式是一條途徑。

但狀態模式還有一點須要注意到,當採用子類繼承實現多種具體狀態的時候,注意控制狀態的數量,以避免出現子類數量膨脹的現象(在使用TypeScriptJava等更完整面向對象語言時)。

相關文章
相關標籤/搜索