業務代碼開發久了,偶爾看看設計模式,總會讓本身有一種清新脫俗的感受。總想把這種感受記下來,但一想到要先起個恰如其分的標題和開頭,就讓我有一種百爪撓心的糾結,因此遲遲沒有開始。今天起更新我學習設計模式筆記的緣由,就好像是,你喜歡一個女孩久了,卻總不表白,難道不怕被別人截胡了麼!
首先咱們來一塊兒設想一些場景:javascript
除了電梯調度、紅綠燈控制,軟件設計和業務開發中,相似諸如狀態切換的問題不難遇到。他們的共同點是:場景存在多個狀態,狀態改變時會觸發對應的不一樣處理方法,狀態間切換又存在諸多約束和限制。前端
面對這種場景,你腦海裏的第一解決方案是什麼?條件分支if...else
或者switch...case
麼?其實應當視具體場景複雜度來看,若是狀態少邏輯簡單,條件分支力所能及。但假若狀態較多,邏輯複雜,又存在諸多特殊狀況的約束限制,本文將介紹的狀態模式歡迎來解一下。java
定義:當一個對象的內在狀態改變時,容許改變其行爲,這個對象看起來像是改變了其類
咱們先來看下傳統面嚮對象語言的類圖,後面再細說前端js中該如何應用
對於一個if...else
處理的長流程,咱們能夠抽象成多個狀態的切換,不一樣具體的狀態繼承自一個抽象狀態接口。場景類維護當前狀態對象的一個實例,以及狀態切換間的約束關係。程序員
這樣作的好處是將狀態的獲取和狀態的切換進行了分離,一個具體的狀態類只處理本狀態相關的邏輯,符合單一職責原則,後期若是新加狀態只須要新建具體的狀態類,符合開放封閉原則。算法
JavaScript沒有抽象接口類的概念,全部類圖也大大簡化,以下: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...else
和switch...case
都是有限狀態機模型,只是平時沒有意識到吧了。在處理較爲複雜的邏輯時,考慮把業務邏輯抽象成一個有限狀態機模型,經常會是代碼邏輯清晰,結構規整。函數
有限狀態機能夠概括出四個要素:工具
Tips:避免把某個程序 動做看成是一種 狀態來處理,動做是不穩定的,即便條件沒有觸發,一旦動做執行完也就結束了;但狀態是穩定的,若是沒有外部條件觸發,狀態會一直持續下去。
介紹了有限狀態機,咱們固然能夠經過上面介紹的狀態模式的方式,來將這種模型工具應用到咱們的代碼開發當中。可是你有沒有注意到一個問題?對,代碼不夠優雅,略顯簡陋,不能忍!學習
接下來介紹一個優雅的有限狀態機實現類庫javascript-state-machine,接下來使用這個類庫簡單實現一個Promise的功能,來看一下如何使用。
首先回顧一下Promise
的特色:
Promise
是一個類。Promise
在實例初始化的時候須要傳入一個函數。resolve
和reject
兩個函數,成功的時候調用resolve
,失敗的時候調用reject
。Promise
實例出的對象有一個then
方法,能夠進行鏈式操做。Promise
擁有三種狀態:pending、fulfilled、rejected,能夠從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的基本功能,演示瞭如何使用。
其實重點仍是狀態模式,經過本文介紹能夠很明顯地感覺到其優勢:
if...else
或switch...case
的使用,下降了程序的複雜性,提升了系統的可維護性。記得Martin在《重構》中,提到一個壞的代碼味道「 Long Method」,當你遇到一個方法中包含了一大堆邏輯,作了不少事的時候,你就應該嗅探到一股惡臭味,怎麼去修改,或許考慮使用狀態模式是一條途徑。
但狀態模式還有一點須要注意到,當採用子類繼承實現多種具體狀態的時候,注意控制狀態的數量,以避免出現子類數量膨脹的現象(在使用TypeScript
或Java
等更完整面向對象語言時)。