摘要:有限狀態(tài)機可以歸納出四個要素現(xiàn)態(tài)即當前的狀態(tài)。但狀態(tài)模式還有一點需要注意到,當采用子類繼承實現(xiàn)多種具體狀態(tài)的時候,注意控制狀態(tài)的數(shù)量,以免出現(xiàn)子類數(shù)量膨脹的現(xiàn)象在使用或等更完整面向對象語言時。
業(yè)務代碼開發(fā)久了,偶爾看看設計模式,總會讓自己有一種清新脫俗的感覺。總想把這種感覺記下來,但一想到要先起個恰如其分的標題和開頭,就讓我有一種百爪撓心的糾結,所以遲遲沒有開始。今天起更新我學習設計模式筆記的原因,就好像是,你喜歡一個女孩久了,卻總不表白,難道不怕被別人截胡了么!
首先我們來一起設想一些場景:
程序員們在等電梯的時候,聊天頻率最高的一個話題,是不是電梯調(diào)度算法呢。寫字樓的幾部電梯到底是分單雙層運力快,還是高低層運力快?當你按下電梯時,是就近樓層的電梯視來接你,還是自顧自的先上后下順帶來接你?
開車來到路口,對紅綠燈亮滅的長短是否曾有過習慣性的吐槽
除了電梯調(diào)度、紅綠燈控制,軟件設計和業(yè)務開發(fā)中,類似諸如狀態(tài)切換的問題不難遇到。他們的共同點是:場景存在多個狀態(tài),狀態(tài)改變時會觸發(fā)對應的不同處理方法,狀態(tài)間切換又存在諸多約束和限制。
面對這種場景,你腦海里的第一解決方案是什么?條件分支if...else或者switch...case么?其實應當視具體場景復雜度來看,如果狀態(tài)少邏輯簡單,條件分支力所能及。但倘若狀態(tài)較多,邏輯復雜,又存在諸多特殊情況的約束限制,本文將介紹的狀態(tài)模式歡迎來解一下。
狀態(tài)模式定義:當一個對象的內(nèi)在狀態(tài)改變時,允許改變其行為,這個對象看起來像是改變了其類
我們先來看下傳統(tǒng)面向對象語言的類圖,后面再細說前端js中該如何應用
對于一個if...else處理的長流程,我們可以抽象成多個狀態(tài)的切換,不同具體的狀態(tài)繼承自一個抽象狀態(tài)接口。場景類維護當前狀態(tài)對象的一個實例,以及狀態(tài)切換間的約束關系。
這樣做的好處是將狀態(tài)的獲取和狀態(tài)的切換進行了分離,一個具體的狀態(tài)類只處理本狀態(tài)相關的邏輯,符合單一職責原則,后期如果新加狀態(tài)只需要新建具體的狀態(tài)類,符合開放封閉原則。
JavaScript沒有抽象接口類的概念,所有類圖也大大簡化,如下:
State類為狀態(tài)類,包含狀態(tài)值以及狀態(tài)改變時具體的處理方法。Context類為場景類,維護狀態(tài)間的約束關系以及控制觸發(fā)狀態(tài)的切換。
下面我們看下代碼,讓概念平穩(wěn)落地:
// 定義狀態(tài)類 class State { constructor(color) { this.color = color } // 處理該狀態(tài)下具體邏輯 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() // 實例出具體狀態(tài) 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())有限狀態(tài)機
狀態(tài)模式脫胎自有限狀態(tài)機(Finite-State-Machine),這個數(shù)學模型描述了有限個狀態(tài),以及這些狀態(tài)之間轉移和動作的行為,是一種對象行為建模的工具。類似下圖就是一種有限狀態(tài)機:
其實我們在處理業(yè)務邏輯時,經(jīng)常打交道的各種事件和狀態(tài)切換,寫的各種if...else和switch...case都是有限狀態(tài)機模型,只是平時沒有意識到吧了。在處理較為復雜的邏輯時,考慮把業(yè)務邏輯抽象成一個有限狀態(tài)機模型,常常會是代碼邏輯清晰,結構規(guī)整。
有限狀態(tài)機可以歸納出四個要素:
現(xiàn)態(tài):即當前的狀態(tài)。
條件:又稱為“事件”。當一個條件被滿足,將會觸發(fā)一個動作,或者執(zhí)行一次狀態(tài)的遷移。
動作:條件滿足后執(zhí)行的動作。動作執(zhí)行完畢后,可以遷移到新的狀態(tài),也可以仍舊保持原狀態(tài)。動作不是必需的,當條件滿足后,也可以不執(zhí)行任何動作,直接遷移到新狀態(tài)。
次態(tài):條件滿足后要遷往的新狀態(tài)。次態(tài)是相對于現(xiàn)態(tài)而言的,次態(tài)一旦被激活,就轉變成新的現(xiàn)態(tài)了。
Tips:避免把某個程序動作當作是一種狀態(tài)來處理,動作是不穩(wěn)定的,即使條件沒有觸發(fā),一旦動作執(zhí)行完也就結束了;但狀態(tài)是穩(wěn)定的,如果沒有外部條件觸發(fā),狀態(tài)會一直持續(xù)下去。
介紹了有限狀態(tài)機,我們當然可以通過上面介紹的狀態(tài)模式的方式,來將這種模型工具應用到我們的代碼開發(fā)當中。但是你有沒有注意到一個問題?對,代碼不夠優(yōu)雅,略顯簡陋,不能忍!
接下來介紹一個優(yōu)雅的有限狀態(tài)機實現(xiàn)類庫javascript-state-machine,接下來使用這個類庫簡單實現(xiàn)一個Promise的功能,來看一下如何使用。
Promise簡單實現(xiàn)首先回顧一下Promise的特點:
Promise是一個類。
Promise在實例初始化的時候需要傳入一個函數(shù)。
傳入的函數(shù)需要接收resolve和reject兩個函數(shù),成功的時候調(diào)用resolve,失敗的時候調(diào)用reject。
Promise實例出的對象有一個then方法,可以進行鏈式操作。
Promise擁有三種狀態(tài):pending、fulfilled、rejected,可以從pending->fulfilled,或pending->rejected,但不能逆向。
接下來上代碼實現(xiàn)一下
// 狀態(tài)機模型 const fsm = new StateMachine({ // 初始狀態(tài) init: "pending", // 狀態(tài)遷移規(guī)則,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 } }總結
本文介紹了狀態(tài)模式和有限狀態(tài)機的概念,以及才開發(fā)中優(yōu)雅使用的姿勢javascript-state-machine,并通過用其簡單實現(xiàn)了Promise的基本功能,演示了如何使用。
其實重點還是狀態(tài)模式,通過本文介紹可以很明顯地感受到其優(yōu)點:
結構清晰,避免了過多的if...else或switch...case的使用,降低了程序的復雜性,提高了系統(tǒng)的可維護性。
很好遵循了開放封閉原則和單一職責原則。
記得Martin在《重構》中,提到一個壞的代碼味道“Long Method”,當你遇到一個方法中包含了一大堆邏輯,做了很多事的時候,你就應該嗅探到一股惡臭味,怎么去修改,或許考慮使用狀態(tài)模式是一條途徑。
但狀態(tài)模式還有一點需要注意到,當采用子類繼承實現(xiàn)多種具體狀態(tài)的時候,注意控制狀態(tài)的數(shù)量,以免出現(xiàn)子類數(shù)量膨脹的現(xiàn)象(在使用TypeScript或Java等更完整面向對象語言時)。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://www.ezyhdfw.cn/yun/99129.html
摘要:函數(shù)式編程前端掘金引言面向對象編程一直以來都是中的主導范式。函數(shù)式編程是一種強調(diào)減少對程序外部狀態(tài)產(chǎn)生改變的方式。 JavaScript 函數(shù)式編程 - 前端 - 掘金引言 面向對象編程一直以來都是JavaScript中的主導范式。JavaScript作為一門多范式編程語言,然而,近幾年,函數(shù)式編程越來越多得受到開發(fā)者的青睞。函數(shù)式編程是一種強調(diào)減少對程序外部狀態(tài)產(chǎn)生改變的方式。因此,...
摘要:代理模式,迭代器模式,單例模式,裝飾者模式最少知識原則一個軟件實體應當盡可能少地與其他實體發(fā)生相互作用。迭代器模式可以將迭代的過程從業(yè)務邏輯中分離出來,在使用迭代器模式之后,即不用關心對象內(nèi)部構造也可以按順序訪問其中的每個元素。 接手項目越來越復雜的時候,有時寫完一段代碼,總感覺代碼還有優(yōu)化的空間,卻不知道從何處去下手。設計模式主要目的是提升代碼可擴展性以及可閱讀性。 本文主要以例子的...
摘要:至于如何優(yōu)雅地管理使用,再次祭出潘神的文章手摸手,帶你優(yōu)雅的使用掘金項目的后端接口文檔我是用的進行的管理,其實有很多強大的功能,不僅僅是一個接口測試工具,接口文檔管理就是其中一個。 首先放個線上地址大家感受一下(由于后端用的是 leancloud 的免費套餐,因此可能會比較慢): vue-data-board P.S. 建議大家盡量自己注冊一個賬號(可以隨便填一個密碼),如果用默認的測...
摘要:它們是單向數(shù)據(jù)流和狀態(tài)容器,而不是狀態(tài)管理。幾個月之前我開始尋找可以解決狀態(tài)管理問題的模式,最終我發(fā)現(xiàn)了狀態(tài)機的概念。狀態(tài)機不接受沒有明確定義的輸入作為當前的狀態(tài)。狀態(tài)機強制開發(fā)者以聲明式的方式思考。 最近我開始思考React應用的狀態(tài)管理。我已經(jīng)取得一些有趣的結論,并且在這篇文章里我會向你展示我們所謂的狀態(tài)管理并不是真的在管理狀態(tài)。 譯者:阿里云前端-也樹 原文鏈接:managing...
摘要:簡介狀態(tài)模式允許一個對象在其內(nèi)部狀態(tài)改變的時候改變它的行為,對象看起來似乎修改了它的類。狀態(tài)通常為一個或多個枚舉常量的表示。簡而言之,當遇到很多同級或者的時候,可以使用狀態(tài)模式來進行簡化。 1. 簡介 狀態(tài)模式(State)允許一個對象在其內(nèi)部狀態(tài)改變的時候改變它的行為,對象看起來似乎修改了它的類。其實就是用一個對象或者數(shù)組記錄一組狀態(tài),每個狀態(tài)對應一個實現(xiàn),實現(xiàn)的時候根據(jù)狀態(tài)挨個去運...
閱讀 2934·2023-04-26 02:23
閱讀 1764·2021-11-11 16:55
閱讀 3210·2021-10-19 11:47
閱讀 3445·2021-09-22 15:15
閱讀 2039·2019-08-30 15:55
閱讀 1102·2019-08-29 15:43
閱讀 1360·2019-08-29 13:16
閱讀 2261·2019-08-29 12:38