状态模式 State Pattern
介绍
- 一个对象有状态变化
- 每次状态变化都会触发一个逻辑
- 不能总是用 if…else 来控制
示例
电灯程序
有一个电灯,电灯上面只有一个开关。电灯开着的时候,按下开关,电灯会切换到关闭状态;再按一次开关,电灯又被打开。同一个开关按钮,在不同的状态下,表现出来的行为是不一样的。
先定义一个 Light 类,电灯对象 light 将从 Light类中创建。light对象有两个属性,用state来记录电灯当前的状态,button表示具体的开关按钮。
class Light { constructor() { this.state = 'off' this.button = null }
init() { const btnDom = document.createElement('button') btnDom.innerHTML = '开关' this.button = document.body.appendChild(btnDom) this.button.onclick = () => { this.buttonPressHandler() } }
buttonPressHandler() { if (this.state === 'off') { console.log('开灯') this.state = 'on' } else if (this.state === 'on') { console.log('关灯') this.state = 'off' } }}
const light = new Light()light.init()我们知道电灯有很多种,比如护眼灯可以切弱光灯和强光灯,开关按一下是弱光,按两下是强光,第三下才是关灯
buttonPressHandler() { if (this.state === 'off') { console.log('开弱光') this.state = 'weakLight' } else if (this.state === 'weakLight') { console.log('开强光') this.state = 'strongLight' } else if (this.state === 'strongLight') { console.log('关灯') this.state = 'off' }}如果像上面这样搞,每次新增或者修改 light 的状态都需要改动 buttonPressHandler 里面的内容,这是违反开放封闭原则的。
通常情况我们说封装,一般都会优先封装对象的行为而不是对象的状态。状态模式却不是如此,状态模式的关键点是把事物的每种状态都封装成单独的类,跟状态有关的行为都被封装在这个类的内部。
改用状态模式的话,针对上面的例子需要定义 3 个状态类,分别是OffLightState、WeakLightState和StrongLightState, 这 3 个类都有一个原型方法 buttonPressHandler,代表在各自状态下,按钮被按下时将发生的行为。
class OffLightState { constructor(light) { this.light = light }
buttonPressHandler() { console.log('弱光') this.light.setState(this.light.weakLightState) }}
class WeakLightState { constructor(light) { this.light = light }
buttonPressHandler() { console.log('强光') this.light.setState(this.light.strongLightState) }}
class OffLightState { constructor(light) { this.light = light }
buttonPressHandler() { console.log('关灯') this.light.setState(this.light.offLightState) }}下面改写 Light 类,对每一个状态类都创建一个状态对象
class Light { constructor() { this.offLightState = new OffLightState(this) this.weakLightState = new WeakLightState(this) this.strongLightState = new StrongLightState(this) this.button = null }}通过 currentState.buttonPressHandler()将请求委托给当前持有的状态对象去执行
class Light { constructor() { this.offLightState = new OffLightState(this) this.weakLightState = new WeakLightState(this) this.strongLightState = new StrongLightState(this) this.button = null }
setState(newState) { this.currentState = newState }
init() { const btnDom = document.createElement("button") this.button = document.body.appendChild(btnDom) this.botton.innerHTML = '开关' this.currentState = this.offLightState this.button.onclick = () => { this.currentState.buttonPressHandler() } }}最终代码如下👇🏻
class OffLightState { constructor(light) { this.light = light }
buttonPressHandler() { console.log('弱光') this.light.setState(this.light.weakLightState) }}
class WeakLightState { constructor(light) { this.light = light }
buttonPressHandler() { console.log('强光') this.light.setState(this.light.strongLightState) }}
class StrongLightState { constructor(light) { this.light = light }
buttonPressHandler() { console.log('关灯') this.light.setState(this.light.offLightState) }}
class Light { constructor() { this.offLightState = new OffLightState(this) this.weakLightState = new WeakLightState(this) this.strongLightState = new StrongLightState(this) this.button = null }
setState(newState) { this.currentState = newState }
init() { const btnDom = document.createElement("button") btnDom.innerHTML = '开关' this.button = document.body.appendChild(btnDom) this.currentState = this.offLightState this.button.onclick = () => { this.currentState.buttonPressHandler() } }}
const light = new Light()light.init()这样的话,如果哪天新增加了一种新的状态,比如超强光,那么就只需要增加一个新的状态类,再在 Light 类中新增一个对象。 观察发现状态类中有一些共同的行为方法,Context(及类也称作上下文) 最终会将请求委托给状态对象的这些方法。即上面例子中的 buttonPressHandler。 如果某次忘记给状态子类实现 buttonPressHandler 方法,就很糟糕了。所以这里我们可以搞一个抽象父类,让状态子类去继承,并在抽象父类中留下抛异常的行为。
class State { buttonPressHandler() { throw new Error("父类的 buttonPressHandler 方法必须被重写") }}
class OffLightState extends State { constructor(light) { super() this.light = light }
buttonPressHandler() { console.log('弱光') this.light.setState(this.light.weakLightState) }}
class WeakLightState extends State { constructor(light) { super() this.light = light }
buttonPressHandler() { console.log('强光') this.light.setState(this.light.strongLightState) }}
class StrongLightState extends State { constructor(light) { super() this.light = light }
buttonPressHandler() { console.log('关灯') this.light.setState(this.light.offLightState) }}
class Light { constructor() { this.offLightState = new OffLightState(this) this.weakLightState = new WeakLightState(this) this.strongLightState = new StrongLightState(this) this.button = null }
setState(newState) { this.currentState = newState }
init() { const btnDom = document.createElement("button") btnDom.innerHTML = '开关' this.button = document.body.appendChild(btnDom) this.currentState = this.offLightState this.button.onclick = () => { this.currentState.buttonPressHandler() } }}
const light = new Light()light.init()交通信号灯不同颜色的变化
类图:
classDiagramContext --> Stateclass State { - color:String +handle(context)}
class Context { - state +getState() State +setState(state)}// 状态(红黄绿)class State { constructor(color) { this.color = color } handle(context) { console.log(`turn to ${this.color} light`) context.setState(this) }}
class Context { constructor() { this.state = null } getState() { return this.state } setState(state) { this.state = state }}
const context = new Context()const red = new State('red')const green = new State('green')const yellow = new State('yellow')// 绿灯亮green.handle(context)console.log(context.getState())应用场景
有限状态机
javascript-state-machine
收藏 - 取消收藏
<button id="btn"> ❤ </button><script>import StateMachine from 'javascript-state-machine'
const btn = document.getElementById('btn');
const fsm = StateMachine({ init: '未收藏', transitions: [ { name: 'doStore', from: '未收藏', to: '已收藏' }, { name: 'deleteStore', from: '已收藏', to: '未收藏' } ], methods: { // 收藏 onDoStore: function(){ console.log('收藏成功') updateText() }, // 取消收藏 onDeleteStore: function() { console.log('取消收藏') updateText() } }})
btn.addEventListener('click', () => { if (fsm.is('未收藏')) { fsm.doStore() } else { fsm.deleteStore() }})
function updateText() { btn.innerText = fsm.state}
updateText()</script>手写简陋 Promise
import StateMachine from 'javascript-state-machine'
class MyPromise { constructor(fn) { this.fsm = new StateMachine({ init: 'pending', transitions: [ { name: 'resolve', from: 'pending', to: 'fullfilled' }, { name: 'reject', from: 'pending', to: 'rejected' } ], methods: { onResolve: function(state, data) { data.successList.forEach(fn => fn()) }, onReject: function(state, data) { data.failList.forEach(fn => fn()) } } }) this.successList = []; this.failList = []
fn(() => { fsm.resolve(this) }, () => { fsm.reject(this) }) }
then(successFn, failFn) { this.successList.push(successFn) this.failList.push(failFn) }}
export default MyPromise设计原则验证
- 将状态对象和主题对象分离,状态的变化逻辑单独处理
- 符合开放封闭原则