业务代码开发久了,偶尔看看设计模式,总会让本身有一种清新脱俗的感受。总想把这种感受记下来,但一想到要先起个恰如其分的标题和开头,就让我有一种百爪挠心的纠结,因此迟迟没有开始。今天起更新我学习设计模式笔记的缘由,就好像是,你喜欢一个女孩久了,却总不表白,难道不怕被别人截胡了么!
首先咱们来一块儿设想一些场景: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
等更完整面向对象语言时)。