狀態模式的關鍵是區分事物內部的狀態,事物內部狀態的改變往往會帶來事物的行為改變。
故事背景
我們來想象這樣一個場景:有一個電燈,電燈上面只有一個開關。當電燈開著的時候,此時 按下開關,電燈會切換到關閉狀態;
再按一次開關,電燈又將被開啟。同一個開關按鈕,在不同 的狀態下,表現出來的行為是不一樣的
程式碼實現(未使用狀態模式)
var Light = function(){
this.state = 'off';
// 給電燈設定初始狀態 off this.button = null;
// 電燈開關按鈕
};
Light.prototype.init = function(){
var button = document.createElement( 'button' ), self = this;
button.innerHTML = '開關';
this.button = document.body.appendChild( button );
this.button.onclick = function(){
self.buttonWasPressed();
}
};
Light.prototype.buttonWasPressed = function(){
if ( this.state === 'off' ){
console.log( '開燈' );
this.state = 'on';
} else if ( this.state === 'on' ){
console.log( '關燈' );
this.state = 'off';
}
};
var light = new Light();
light.init();
複製程式碼
存在的問題
假如現在電燈的狀態多了一種,第一次按下開啟弱光,第二次按下開啟強光,第三次才是關閉電燈。以上的程式碼就無法滿足這種電燈的情況
分析
狀態模式的關鍵是把事物的 每種狀態都封裝成單獨的類,跟此種狀態有關的行為都被封裝在這個類的內部,所以 button 被按下的的時候,只需要在上下文中,把這個請求委託給當前的狀態物件即可,該狀態物件會負責渲染它自身的行為。如下圖所示
重構思路
- 定義 3 個狀態類
- 改寫 Light 類,使用狀態物件記錄當前的狀態
- 提供一個 方法來切換 light 物件的狀態
/******************** 定義 3 個狀態類 ************************/// OffLightState:var OffLightState = function( light ){
this.light = light;
};
OffLightState.prototype.buttonWasPressed = function(){
console.log( '弱光' );
// offLightState 對應的行為 this.light.setState( this.light.weakLightState );
// 切換狀態到 weakLightState
};
// WeakLightState:var WeakLightState = function( light ){
this.light = light;
};
WeakLightState.prototype.buttonWasPressed = function(){
console.log( '強光' );
// weakLightState 對應的行為 this.light.setState( this.light.strongLightState );
//切換狀態到 strongLightState
};
// StrongLightState:var StrongLightState = function( light ){
this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function(){
console.log( '關燈' );
// strongLightState 對應的行為 this.light.setState( this.light.offLightState );
// 切換狀態到 offLightState
};
/******************* 改寫 Light 類,使用狀態物件記錄當前的狀態 ******************/var Light = function(){
this.offLightState = new OffLightState( this );
this.weakLightState = new WeakLightState( this );
this.strongLightState = new StrongLightState( this );
this.button = null;
};
/******************** 提供一個 方法來切換 light 物件的狀態 ************************/Light.prototype.init = function(){
var button = document.createElement( 'button' ), self = this;
this.button = document.body.appendChild( button );
this.button.innerHTML = '開關';
this.currState = this.offLightState;
this.button.onclick = function(){
self.currState.buttonWasPressed();
}
};
Light.prototype.setState = function( newState ){
this.currState = newState;
};
var light = new Light();
light.init();
複製程式碼
改進效果
執行結果跟之前的程式碼一致,但是使用狀態模式的好處很明顯,它可以使每 一種狀態和它對應的行為之間的關係區域性化,這些行為被分散和封裝在各自對應的狀態類之中, 便於閱讀和管理程式碼。另外,狀態之間的切換都被分佈在狀態類內部,這使得我們無需編寫過多的 if、else 條件 分支語言來控制狀態之間的轉換。
總結
- 狀態模式定義了狀態與行為之間的關係,並將它們封裝在一個類裡。通過增加新的狀態 類,很容易增加新的狀態和轉換。
- 避免 Context 無限膨脹,狀態切換的邏輯被分佈在狀態類中,也去掉了 Context 中原本過 5 多的條件分支。
- 用物件代替字串來記錄當前狀態,使得狀態的切換更加一目瞭然。
- Context 中的請求動作和狀態類中封裝的行為可以非常容易地獨立變化而互不影響