JavaScript:設計模式之狀態模式
介紹
狀態模式(State)允許一個對象在其內部狀態改變的時候改變它的行為,對象看起來似乎修改了它的類。
本文
舉個例子,就比如我們平時在下載東西,通常就會有好幾個狀態,比如準備狀態(ReadyState)、下載狀態(DownloadingState)、暫停狀態(DownloadPausedState)、下載完畢狀態(DownloadedState)、失敗狀態(DownloadFailedState),也就是說在每個狀態都只可以做目前狀態才可以做的事情,而不能做其它狀態能做的事兒。
由於State模式描述了下載(Download)如何在每一種狀態下表現出不同的行為。這一模式的關鍵思想就是引入了一個叫做State的抽象類別(或JS裡的函數)來表示下載狀態,State函數(作為原型)為每個狀態的子類(繼承函數)聲明了一些公用介面。其每個繼承函數實現與特定狀態相關的行為,比如DownloadingState和DownloadedState分別實現了正在下載和下載完畢的行為。這些行為可以通過Download來來維護。
讓我們來實現一把,首先定義作為其他基礎函數的原型的State函數:
var State = function () {};State.prototype.download = function () { throw new Error(該方法必須被重載!);};State.prototype.pause = function () { throw new Error(該方法必須被重載!);};State.prototype.fail = function () { throw new Error(該方法必須被重載!);};State.prototype.finish = function () { throw new Error(該方法必須被重載!);};
我們為State的原型定義了4個方法介面,分別對應著下載(download)、暫停(pause)、失敗(fail)、結束(finish)以便子函數可以重寫。
在編寫子函數之前,我們先來編寫一個ReadyState函數,以便可以將狀態傳遞給第一個download狀態:
var ReadyState = function (oDownload) { State.apply(this); this.oDownload = oDownload;};ReadyState.prototype = new State();ReadyState.prototype.download = function () { this.oDownload.setState(this.oDownload.getDownloadingState()); // Ready以後,可以開始下載,所以設定了Download函數裡的狀態擷取方法 console.log(Start Download!);};ReadyState.prototype.pause = function () { throw new Error(還沒開始下載,不能暫停!);};ReadyState.prototype.fail = function () { throw new Error(檔案還沒開始下載,怎麼能說失敗呢!);};ReadyState.prototype.finish = function () { throw new Error(檔案還沒開始下載,當然也不能結束了!);};
該函數接收了一個Download維護函數的執行個體作為參數,Download函數用於控制狀態的改變和擷取(類似於中央控制器,讓外部調用),ReadyState重寫了原型的download方法,以便開始進行下載。我們繼續來看Download函數的主要功能:
var Download = function () { this.oState = new ReadyState(this);};Download.prototype.setState = function (oState) { this.oState = oState;};// 對外暴露的四個公用方法,以便外部調用Download.prototype.download = function () { this.oState.download();};Download.prototype.pause = function () { this.oState.pause();};Download.prototype.fail = function () { this.oState.fail();};Download.prototype.finish = function () { this.oState.finish();};//擷取各種狀態,傳入當前this對象Download.prototype.getReadyState = function () { return new ReadyState(this);};Download.prototype.getDownloadingState = function () { return new DownloadingState(this);};Download.prototype.getDownloadPausedState = function () { return new DownloadPausedState(this);};Download.prototype.getDownloadedState = function () { return new DownloadedState(this);};Download.prototype.getDownloadedFailedState = function () { return new DownloadFailedState(this);};
Download函數的原型提供了8個方法,4個是對用於下載狀態的操作行為,另外4個是用於擷取當前四個不同的狀態,這4個方法都接收this作為參數,也就是將Download執行個體自身作為一個參數傳遞給處理該請求的狀態物件(ReadyState 以及後面要實現的繼承函數),這使得狀態物件比必要的時候可以訪問oDownlaod。
接下來,繼續定義4個相關狀態的函數:
var DownloadingState = function (oDownload) { State.apply(this); this.oDownload = oDownload;};DownloadingState.prototype = new State();DownloadingState.prototype.download = function () { throw new Error(檔案已經正在下載中了!);};DownloadingState.prototype.pause = function () { this.oDownload.setState(this.oDownload.getDownloadPausedState()); console.log(暫停下載!);};DownloadingState.prototype.fail = function () { this.oDownload.setState(this.oDownload.getDownloadedFailedState()); console.log(下載失敗!);};DownloadingState.prototype.finish = function () { this.oDownload.setState(this.oDownload.getDownloadedState()); console.log(下載完畢!);};
DownloadingState的主要注意事項就是已經正在下載的檔案,不能再次開始下載了,其它的狀態都可以連續進行。
var DownloadPausedState = function (oDownload) { State.apply(this); this.oDownload = oDownload;};DownloadPausedState.prototype = new State();DownloadPausedState.prototype.download = function () { this.oDownload.setState(this.oDownload.getDownloadingState()); console.log(繼續下載!);};DownloadPausedState.prototype.pause = function () { throw new Error(已經暫停了,咋還要暫停呢!);};DownloadPausedState.prototype.fail = function () { this.oDownload.setState(this.oDownload.getDownloadedFailedState()); console.log(下載失敗!);};DownloadPausedState.prototype.finish = function () { this.oDownload.setState(this.oDownload.getDownloadedState()); console.log(下載完畢!);};
DownloadPausedState函數裡要注意的是,已經暫停下載,不能再次暫停。
var DownloadedState = function (oDownload) { State.apply(this); this.oDownload = oDownload;};DownloadedState.prototype = new State();DownloadedState.prototype.download = function () { this.oDownload.setState(this.oDownload.getDownloadingState()); console.log(重新下載!);};DownloadedState.prototype.pause = function () { throw new Error(對下載完了,還暫停啥?);};DownloadedState.prototype.fail = function () { throw new Error(都下載成功了,咋會失敗呢?);};DownloadedState.prototype.finish = function () { throw new Error(下載成功了,不能再為成功了吧!);};
DownloadedState函數,同理成功下載以後,不能再設定finish了,只能設定重新下載狀態。
var DownloadFailedState = function (oDownload) { State.apply(this); this.oDownload = oDownload;};DownloadFailedState.prototype = new State();DownloadFailedState.prototype.download = function () { this.oDownload.setState(this.oDownload.getDownloadingState()); console.log(嘗試重新下載!);};DownloadFailedState.prototype.pause = function () { throw new Error(失敗的下載,也不能暫停!);};DownloadFailedState.prototype.fail = function () { throw new Error(都失敗了,咋還失敗呢!);};DownloadFailedState.prototype.finish = function () { throw new Error(失敗的下載,肯定也不會成功!);};同理,DownloadFailedState函數的失敗狀態,也不能再次失敗,但可以和finished以後再次嘗試重新下載。
調用測試代碼,就非常簡單了,我們在HTML裡示範吧,首先是要了jquery,然後有3個按鈕分別代表:開始下載、暫停、重新下載。(注意在Firefox裡用firebug查看結果,因為用了 console.log方法)。
<script type=text/javascript src=/jquery.js></script><script type=text/javascript src=Download.js></script><script type=text/javascript src=states/State.js></script><script type=text/javascript src=states/DownloadFailedState.js></script><script type=text/javascript src=states/DownloadPausedState.js></script><script type=text/javascript src=states/DownloadedState.js></script><script type=text/javascript src=states/DownloadingState.js></script><script type=text/javascript src=states/ReadyState.js></script> <script type=text/javascript> var oDownload = new Download(); $(#download_button).click(function () { oDownload.download(); }); $(#pause_button).click(function () { oDownload.pause(); }); $(#resume_button).click(function () { oDownload.download(); }); </script>總結
狀態模式的使用情境也特別明確,有如下兩點:
- 一個對象的行為取決於它的狀態,並且它必須在運行時刻根據狀態改變它的行為。
- 一個操作中含有大量的分支語句,而且這些分支語句依賴於該對象的狀態。狀態通常為一個或多個枚舉常量的表示。