Javascript設計模式之我見:狀態模式
大家好!本文介紹狀態模式及其在Javascript中的應用。
模式介紹
定義
當一個對象的內在狀態改變時允許改變其行為,這個對象看起來像是改變了其類。
狀態模式主要解決的是控制一個對象狀態的條件表達式過于復雜時的情況。把狀態的判斷邏輯轉移到表示不同狀態的一系列類中,可以把復雜的判斷邏輯簡化。
類圖及說明

State:抽象狀態
接口或抽象類,負責對象狀態定義,并且封裝環境角色以實現狀態切換
ConcreState:具體狀態
每一個具體狀態必須完成兩個職責:本狀態的行為管理以及趨向狀態處理。通俗地說,就是本狀態下要做的事情,以及本狀態如何過渡到其他狀態。
Context:環境
定義客戶端需要的接口,并且負責具體狀態的切換。
應用場景
- 一個對象的行為取決于它的狀態, 并且它必須在運行時刻根據狀態改變它的行為。
- 代碼中包含大量與對象狀態有關的條件語句:一個操作中含有龐大的多分支的條件(if else(或switch case)語句,且這些分支依賴于該對象的狀態。這個狀態通常用一個或多個枚舉常量表示。通常 , 有多個操作包含這一相同的條件結構。 State模式將每一個條件分支放入一個獨立的類中。這使得你可以根據對象自身的情況將對象的狀態作為一個對象,這一對象可以不依賴于其他對象而獨立變化。
優點
- 避免了過多的 swith…case 或者 if..else 語句的使用,避免了程序的復雜性
- 很好的使用體現了開閉原則和單一職責原則,每個狀態都是一個子類,你要增加狀態就增加子類,你要修改狀態,你只修改一個子類就可以了
- 封裝性非常好,這也是狀態模式的基本要求。狀態變換放置到了類的內部來實現,外部的調用不用知道類內部如何實現狀態和行為的變換。
缺點
- 具體狀態類可能會有很多個,不好管理。
狀態模式在Javascript中的應用
我的理解
- ConcreteState具體狀態類有兩個職責:處理本狀態必須完成的任務;過渡到其他狀態。
- 可以采自下而上的方法來實現狀態類,即先實現ConcreteState類,然后再將ConcreteState類的共性提取出來,形成父類State。
類圖及說明

User:使用者
使用者具有不同的狀態,它創建Context類并將狀態的邏輯委托給Context類處理。
示例
小時候大家應該都玩過魂斗羅游戲吧,魂斗羅吃了無敵道具后會變成刀槍不入,吃了火力增強道具后會變成殺人機器。讓我們來看看它的狀態是如何轉換的。
狀態圖

魂斗羅Warrior有NORMAL、INVINCIBLE、POWER、DEAD四個狀態,每個狀態都有beNormal、beInvincible、bePower、dead四個方法。有timeOver、getInvincible、getPower、beShotDead四個觸發狀態的事件。
類圖

代碼
代碼中使用的庫:YOOP
Warrior
var Warrior = YYC.Class({ Private: { _state: null }, Public: { //*事件標志 _getInvincible: false, _getPower: false, _timeOver: false, _beShotDead: false, setState: function (state) { this._state = state; }, //*狀態方法 beNormal: function () { switch (this._state) { case Warrior.NORMAL_STATE: //本狀態beNormal方法的邏輯。已經處于NORMAL狀態,不用再轉換為NORMAL狀態了 console.log("恢復正常"); break; case Warrior.INVINCIBLE_STATE: //INVINCIBLE狀態下beNormal方法的邏輯 console.log("恢復正常"); //從INVINCIBLE狀態轉換到NORMAL狀態 this.setState(Warrior.NORMAL_STATE); break; case Warrior.POWER_STATE: //POWER狀態下beNormal方法的邏輯 console.log("恢復正常"); //從POWER狀態轉換到NORMAL狀態 this.setState(Warrior.NORMAL_STATE); break; case Warrior.DEAD_STATE: //不能起死回生 break; } }, beInvincible: function () { switch (this._state) { case Warrior.NORMAL_STATE: console.log("無敵"); this.setState(Warrior.INVINCIBLE_STATE); break; case Warrior.INVINCIBLE_STATE: console.log("無敵"); break; case Warrior.POWER_STATE: console.log("無敵"); this.setState(Warrior.INVINCIBLE_STATE); break; case Warrior.DEAD_STATE: break; } }, bePower: function () { switch (this._state) { case Warrior.NORMAL_STATE: console.log("火力增強"); this.setState(Warrior.POWER_STATE); break; case Warrior.INVINCIBLE_STATE: console.log("火力增強"); this.setState(Warrior.POWER_STATE); break; case Warrior.POWER_STATE: console.log("火力增強"); break; case Warrior.DEAD_STATE: break; } }, dead: function () { switch (this._state) { case Warrior.NORMAL_STATE: console.log("死亡"); this.setState(Warrior.DEAD_STATE); break; case Warrior.INVINCIBLE_STATE: //都無敵了當然不會死亡 break; case Warrior.POWER_STATE: console.log("死亡"); this.setState(Warrior.DEAD_STATE); break; case Warrior.DEAD_STATE: console.log("死亡"); break; } }, action: function () { //*此處進行觸發狀態的事件判斷 if (this._timeOver) { this.beNormal(); } else if (this._getInvincible) { this.beInvincible(); } else if (this._getPower) { this.bePower(); } else if (this._beShotDead) { this.dead(); } } }, Static: { NORMAL_STATE: 1, INVINCIBLE_STATE: 2, POWER_STATE: 3, DEAD_STATE: 4 } });
場景類
function _resetFlag(warrior) { warrior._getInvincible = false; warrior._getPower = false; warrior._timeOver = false; warrior._beShotDead = false; } function _getInvincible(warrior) { _resetFlag(warrior); warrior._getInvincible = true; } function _getPower(warrior) { _resetFlag(warrior); warrior._getPower = true; } function _beShotDead(warrior) { _resetFlag(warrior); warrior._beShotDead = true; } function main() { var warrior = new Warrior(); //初始狀態為Normal warrior.setState(Warrior.NORMAL); //獲得無敵道具,進入無敵狀態 _getInvincible(warrior); warrior.action(); //獲得火力增強道具,進入火力增強狀態 _getPower(warrior); warrior.action(); //被擊中,進入死亡狀態 _beShotDead(warrior); warrior.action(); }
運行結果

示例分析
我們來看看這段程序的問題。
- 實現類Warrior用了大量的switch...case判斷,增加了大量代碼,可讀性和可維護性差。
- 擴展性差。
如果要增加1個狀態,則beNormal、beInvincible、bePower、dead方法中都要增加判斷條件,這不符合開閉原則。
使用狀態模式
類圖

代碼
State
var State = YYC.AClass({ Public: { setContext: function (context) { this.P_context = context; } }, Protected: { P_context: null }, Abstract: { beNormal: function () { }, beInvincible: function () { }, bePower: function () { }, dead: function () { } } });
NormalState
var NormalState = YYC.Class(State, { Public: { //*在具體狀態類中進行觸發狀態的事件判斷 beNormal: function () { if (this.P_context.warrior.timeOver) { //本狀態邏輯 console.log("恢復正常"); } }, beInvincible: function () { if (this.P_context.warrior.getInvincible) { //過度到無敵狀態的邏輯 this.P_context.setState(Context.InvincibleState); this.P_context.beInvincible(); } }, bePower: function () { if (this.P_context.warrior.getPower) { //過度到火力增強狀態的邏輯 this.P_context.setState(Context.PowerState); this.P_context.bePower(); } }, dead: function () { if (this.P_context.warrior.beShotDead) { //過度到死亡狀態的邏輯 this.P_context.setState(Context.DeadState); this.P_context.dead(); } } } });
InvincibleState
var InvincibleState = YYC.Class(State, { Public: { beNormal: function () { if (this.P_context.warrior.timeOver) { this.P_context.setState(Context.NormalState); this.P_context.beNormal(); } }, beInvincible: function () { if (this.P_context.warrior.getInvincible) { console.log("無敵"); } }, bePower: function () { if (this.P_context.warrior.getPower) { this.P_context.setState(Context.PowerState); this.P_context.bePower(); } }, dead: function () { //都無敵了當然不會死亡 } } });
PowerState
var PowerState = YYC.Class(State, { Public: { beNormal: function () { if (this.P_context.warrior.timeOver) { this.P_context.setState(Context.NormalState); this.P_context.beNormal(); } }, beInvincible: function () { if (this.P_context.warrior.getInvincible) { this.P_context.setState(Context.InvincibleState); this.P_context.beInvincible(); } }, bePower: function () { if (this.P_context.warrior.getPower) { console.log("火力增強"); } }, dead: function () { if (this.P_context.warrior.beShotDead) { this.P_context.setState(Context.DeadState); this.P_context.dead(); } } } });
DeadState
var DeadState = YYC.Class(State, { Public: { beNormal: function () { //不能起死回生 }, beInvincible: function () { //掛都掛了,還怎么無敵 }, bePower: function () { //掛都掛了,還怎么火力增強 }, dead: function () { if (this.P_context.warrior.beShotDead) { console.log("死亡"); } } } });
Context
var Context = YYC.Class({ Init: function (warrior) { this.warrior = warrior; }, Private: { _state: null }, Public: { warrior: null, setState: function (state) { this._state = state; //把當前的上下文通知到當前狀態類對象中 this._state.setContext(this); }, beNormal: function () { this._state.beNormal(); }, beInvincible: function () { this._state.beInvincible(); }, bePower: function () { this._state.bePower(); }, dead: function () { this._state.dead(); } }, Static: { NormalState: new NormalState(), InvincibleState: new InvincibleState(), PowerState: new PowerState(), DeadState: new DeadState() } });
Warrior
var Warrior = YYC.Class({ Init: function () { this._context = new Context(this); //設置初始狀態 this._context.setState(Context.NormalState); }, Private: { _context: null }, Public: { //*事件標志 getInvincible: false, getPower: false, timeOver: false, beShotDead: false, action: function () { this._context.beNormal(); this._context.beInvincible(); this._context.bePower(); this._context.dead(); } } });
場景類Client
function _resetFlag(warrior) { warrior.getInvincible = false; warrior.getPower = false; warrior.imeOver = false; warrior.beShotDead = false; } function _getInvincible(warrior) { _resetFlag(warrior); warrior.getInvincible = true; } function _getPower(warrior) { _resetFlag(warrior); warrior.getPower = true; } function _beShotDead(warrior) { _resetFlag(warrior); warrior.beShotDead = true; } function main() { var warrior = new Warrior(); //獲得無敵道具,進入無敵狀態 _getInvincible(warrior); warrior.action(); //獲得火力增強道具,進入火力增強狀態 _getPower(warrior); warrior.action(); //被擊中,進入死亡狀態 _beShotDead(warrior); warrior.action(); }
運行結果

示例分析
將觸發狀態的事件判斷移到Warrior類中
目前是在具體狀態類中進行觸發狀態的事件判斷,這樣造成了重復判斷??梢詫⑴袛嗵岢鰜?,放到Warrior類的action中進行判斷:
Warrior
action: function () { if (this.timeOver) { this._context.beNormal(); } else if (this.getInvincible) { this._context.beInvincible(); } else if (this.getPower) { this._context.bePower(); } else if (this.beShotDead) { this._context.dead(); } }
NormalState(其它三個具體狀態類做相同的重構)
var NormalState = YYC.Class(State, { Public: { beNormal: function () { //本狀態邏輯 console.log("恢復正常"); }, beInvincible: function () { //過度到無敵狀態的邏輯 this.P_context.setState(Context.InvincibleState); this.P_context.beInvincible(); }, bePower: function () { //過度到火力增強狀態的邏輯 this.P_context.setState(Context.PowerState); this.P_context.bePower(); }, dead: function () { //過度到死亡狀態的邏輯 this.P_context.setState(Context.DeadState); this.P_context.dead(); } } });
局限性
如果不同狀態轉換為同一狀態的觸發事件不同,那么就不能把觸發狀態的事件移到Warrior類中,而需要在具體狀態類中判斷。
例如,現在從NORMAL狀態轉換到POWER狀態的觸發事件為“獲得火力增強道具”,而從INVINCIBLE狀態轉換到POWER狀態的觸發事件為“持續時間結束且獲得火力增強道具”:

那么就不能在Warrior中進行統一的事件判斷了,而應該在具體狀態類NormalState、InvincibleState類中判斷:
NormalState
bePower: function () { if (this.P_context.warrior.getPower) { this.P_context.setState(Context.PowerState); this.P_context.bePower(); } },
InvincibleState
bePower: function () { if (this.P_context.warrior.timeOver && this.P_context.warrior.getPower) { this.P_context.setState(Context.PowerState); this.P_context.bePower(); } },
結論
不同狀態轉換為同一狀態的觸發事件相同,則可以將觸發狀態的事件判斷放到調用Context的類中;否則將觸發狀態的事件判斷放到具體狀態類中。
參考資料
浙公網安備 33010602011771號