容许一个对象在其内部状态改变时来改变它的行为,对象看起来彷佛修改了它的类。在状态模式中,咱们把状态封装成独立的类,并将请求委托给当前的状态对象,因此当对象内部的状态改变时,对象会有不一样的行为。状态模式的关键就是区分对象的内部状态。javascript
class Light {
construct () {
this.state = 'off'
this.button = null
}
// 建立一个button负责控制电灯的开关
init () {
const button = document.createElement('button')
this.button = document.body.appendChild(button)
this.button.innerHTML = '开关'
this.button.onclick = () => {
this.buttonWasPressed()
}
}
buttonWasPressed () {
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()
复制代码
上面代码实现了一个强壮的状态机,看起来这段代码设计得无懈可击了,这个程序没有任何Bug。
比较惋惜的是,世界上的电灯并不是都只有开关两种状态,一些酒店里的电灯只有一个开关,可是它的表现是:第一次按下打开弱光,第二次按下打开强光,第三次才是关闭电灯。因而,咱们须要修改前面的代码:java
buttonWasPressed () {
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'
}
复制代码
如今咱们来总结下上面的程序的缺点:算法
首先咱们先肯定电灯的状态种类,而后把它们封装成单独的类,封装通常是封装对象的行为,而不是对象的状态。可是在状态模式中,关键的就是把每种状态封装成单独的类,跟状态相关的行为都封装在类的内部。从以前的代码得知,电灯有三种状态: OffLightState、WeakLightState、StrongLightState。首先编写状态类:性能优化
class OffLightState {
construct (light) {
this.light = light
}
buttonWasPressed () {
console.log('弱光')
this.light.setState(this.light.weakLightState)
}
}
class WeakLightState {
construct (light) {
this.light = light
}
buttonWasPressed () {
console.log('强光')
this.light.setState(this.light.strongLightState)
}
}
class StrongLightState {
construct (light) {
this.light = light
}
buttonWasPressed () {
console.log('关灯')
this.light.setState(this.light.offLightState)
}
}
复制代码
接下来编写Light类,咱们再也不须要一个字符串来记录当前的状态,而是使用更加立体化的状态对象,在初始化Light类的时候就为每个state类建立一个状态对象:闭包
class Light {
construct () {
this.offLightState = new OffLightState(this)
this.weakLightState = new WeakLightState(this)
this.strongLightState = new StrongLightState(this)
this.currentState = this.offLightState // 初始化电灯状态
this.button = null
}
init () {
const button = document.createElement('button')
this.button = document.body.appendChild(button)
this.button.innerHTML = '开关'
this.button.onclick = () => {
this.currentState.buttonWasPressed()
}
}
setState (newState) {
this.currentState = newState
}
}
const light = new Light()
light.init()
复制代码
经过使用状态模式重构以后,咱们看到程序有不少优势:app
在状态模式中,Light类被称为上下文(Context)。Context持有全部状态对象的引用 ,以便把请求委托给状态对象。在上面的例子中,请求最后委托到的是状态类的buttonWasPressed方法,因此全部的状态类都必须实现buttonWasPressed方法。
在Java中,全部的状态类必须继承自一个State抽象类,从而保证全部的状态子类都实现buttonWasPressed方法。遗憾的是,在JavaScript中没有抽象类,也没有接口的概念。咱们能够编写一个状态类,而后实现buttonWasPressed方法,在函数体中抛出错误,若是继承它的子类没有实现buttonWasPressed方法就会在状态切换时抛出异常,这样至少在程序运行期间就能够发现错误,下面优化上面的代码:函数
class State {
buttonWasPressed () {
throw new Error('父类的buttonWasPressed必须被重写')
}
}
class OffLightState extend State {
construct (light) {
this.light = light
}
buttonWasPressed () {
console.log('弱光')
this.light.setState(this.light.weakLightState)
}
}
复制代码
在上面的例子,从性能方面考虑,还有一些能够优化的点:性能
状态模式和策略模式像一对双胞胎,它们都封装了一系列的算法或者行为,他们的类图看起来几乎如出一辙,可是从意图上看它们有很大不一样。
它们的相同点是,都有一个上下文、一些策略类或者状态类,上下文把请求委托给这些类来执行。它们之间的区别是策略模式中的各个策略类之间是平等又平行的,它们之间没有任何关系,因此客户必须熟知这些策略类的做用,以便客户本身能够随时主动切换算法。可是在状态模式中,状态和状态对应的行为早已被封装好,状态之间的切换也早就被规定,“改变行为”这件事发生在状态模式的内部,对于客户来讲,不须要了解这些细节。优化
上面咱们使用的是传统的面向对象的方式实现状态模式,在JavaScript中,没有规定状态对象必定要从类中建立而来。另外,JavaScript能够很是方便利用委托技术,不须要事先让一个对象持有另外一个对象,咱们能够经过Function.prototype.call方法直接把请求委托给某个对象字面来执行。下面看下实现的代码:ui
var FSM = {
off: {
buttonWasPressed: function () {
console.log('关灯')
this.currentState = FSM.on
}
},
on: {
buttonWasPressed: function () {
console.log('开灯')
this.currentState = FSM.off
}
}
}
var Light = function () {
this.currentState = FSM.off // 设置初始状态
this.button = null
}
Light.prototype.init = function () {
var self = this
var button = document.createElement('button')
this.button = document.body.appendChild(button)
this.button.innerHTML = '开关'
this.button.onclick = function () {
self.currentState.buttonWasPressed.call(self) // 把请求委托给状态机FSM
}
}
const light = new Light()
light.init()
复制代码
咱们还可使用闭包来编写这个例子,咱们须要实现一个delegate函数:
var delegate = function (client, delegation) {
return {
buttonWasPressed: function () { // 将客户的请求委托给delegation对象
return delegation.buttonWasPressed.apply(client, arguments)
}
}
}
var FSM = {
off: {
buttonWasPressed: function () {
console.log('关灯')
this.currentState = FSM.on
}
},
on: {
buttonWasPressed: function () {
console.log('开灯')
this.currentState = FSM.off
}
}
}
var Light = function () {
this.offState = delegate(this, FSM.off)
this.onState = delegate(this, FSM.on)
this.currentState = this.offState // 设置初始状态
this.button = null
}
Light.prototype.init = function () {
var self = this
var button = document.createElement('button')
this.button = document.body.appendChild(button)
this.button.innerHTML = '开关'
this.button.onclick = function () {
self.currentState.buttonWasPressed()
}
}
复制代码
在文章中,咱们经过各类方式来实现状态模式,而且对比了使用状态模式先后程序的优缺点,从中咱们也能够得出状态模式的优势和缺点。它的优势以下:
状态模式的缺点:第一,咱们须要在系统中定义许多状态类,编写不少的状态类是一项枯燥泛味的工做,这样也会致使系统中增长不少对象。第二,由于逻辑分散中状态类中,虽然避开了不受欢迎的条件语句,但也形成了逻辑分散的问题,咱们没法在一个地方就看清整个状态机的逻辑。