容許一個對象在其內部狀態改變時來改變它的行爲,對象看起來彷佛修改了它的類。在狀態模式中,咱們把狀態封裝成獨立的類,並將請求委託給當前的狀態對象,因此當對象內部的狀態改變時,對象會有不一樣的行爲。狀態模式的關鍵就是區分對象的內部狀態。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()
}
}
複製代碼
在文章中,咱們經過各類方式來實現狀態模式,而且對比了使用狀態模式先後程序的優缺點,從中咱們也能夠得出狀態模式的優勢和缺點。它的優勢以下:
狀態模式的缺點:第一,咱們須要在系統中定義許多狀態類,編寫不少的狀態類是一項枯燥泛味的工做,這樣也會致使系統中增長不少對象。第二,由於邏輯分散中狀態類中,雖然避開了不受歡迎的條件語句,但也形成了邏輯分散的問題,咱們沒法在一個地方就看清整個狀態機的邏輯。