狀態(tài)模式(State)允許一個對象在其內(nèi)部狀態(tài)改變的時候改變它的行為,對象看起來似乎修改了它的類。
舉個例子,就比如我們平時在下載東西,通常就會有好幾個狀態(tài),比如準(zhǔn)備狀態(tài)(ReadyState)、下載狀態(tài)(DownloadingState)、暫停狀態(tài)(DownloadPausedState)、下載完畢狀態(tài)(DownloadedState)、失敗狀態(tài)(DownloadFailedState),也就是說在每個狀態(tài)都只可以做當(dāng)前狀態(tài)才可以做的事情,而不能做其它狀態(tài)能做的事兒。
由于State模式描述了下載(Download)如何在每一種狀態(tài)下表現(xiàn)出不同的行為。這一模式的關(guān)鍵思想就是引入了一個叫做State的抽象類(或JS里的函數(shù))來表示下載狀態(tài),State函數(shù)(作為原型)為每個狀態(tài)的子類(繼承函數(shù))聲明了一些公共接口。其每個繼承函數(shù)實現(xiàn)與特定狀態(tài)相關(guān)的行為,比如DownloadingState和DownloadedState分別實現(xiàn)了正在下載和下載完畢的行為。這些行為可以通過Download來來維護。
讓我們來實現(xiàn)一把,首先定義作為其他基礎(chǔ)函數(shù)的原型的State函數(shù):
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("該方法必須被重載!"); };
我們?yōu)镾tate的原型定義了4個方法接口,分別對應(yīng)著下載(download)、暫停(pause)、失?。╢ail)、結(jié)束(finish)以便子函數(shù)可以重寫。
在編寫子函數(shù)之前,我們先來編寫一個ReadyState函數(shù),以便可以將狀態(tài)傳遞給第一個download狀態(tài):
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以后,可以開始下載,所以設(shè)置了Download函數(shù)里的狀態(tài)獲取方法 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("文件還沒開始下載,當(dāng)然也不能結(jié)束了!"); };
該函數(shù)接收了一個Download維護函數(shù)的實例作為參數(shù),Download函數(shù)用于控制狀態(tài)的改變和獲取(類似于中央控制器,讓外部調(diào)用),ReadyState重寫了原型的download方法,以便開始進行下載。我們繼續(xù)來看Download函數(shù)的主要功能:
var Download = function () { this.oState = new ReadyState(this); }; Download.prototype.setState = function (oState) { this.oState = oState; }; // 對外暴露的四個公共方法,以便外部調(diào)用 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(); }; //獲取各種狀態(tài),傳入當(dāng)前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函數(shù)的原型提供了8個方法,4個是對用于下載狀態(tài)的操作行為,另外4個是用于獲取當(dāng)前四個不同的狀態(tài),這4個方法都接收this作為參數(shù),也就是將Download實例自身作為一個參數(shù)傳遞給處理該請求的狀態(tài)對象(ReadyState 以及后面要實現(xiàn)的繼承函數(shù)),這使得狀態(tài)對象比必要的時候可以訪問oDownlaod。
接下來,繼續(xù)定義4個相關(guān)狀態(tài)的函數(shù):
var DownloadingState = function (oDownload) { State.apply(this); this.oDownload = oDownload; }; DownloadingState.prototype = new State(); DownloadingState.prototype.download = function () { throw new Error("文件已經(jīng)正在下載中了!"); }; 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的主要注意事項就是已經(jīng)正在下載的文件,不能再次開始下載了,其它的狀態(tài)都可以連續(xù)進行。
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("繼續(xù)下載!"); }; DownloadPausedState.prototype.pause = function () { throw new Error("已經(jīng)暫停了,咋還要暫停呢!"); }; 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函數(shù)里要注意的是,已經(jīng)暫停的下載,不能再次暫停。
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函數(shù),同理成功下載以后,不能再設(shè)置finish了,只能設(shè)置重新下載狀態(tài)。
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函數(shù)的失敗狀態(tài),也不能再次失敗,但可以和finished以后再次嘗試重新下載。
調(diào)用測試代碼,就非常簡單了,我們在HTML里演示吧,首先是要了jquery,然后有3個按鈕分別代表:開始下載、暫停、重新下載。(注意在Firefox里用firebug查看結(jié)果,因為用了 console.log方法)。
<html> <head> <link type="text/css" rel="stylesheet" href="http://www.cnblogs.com/css/style.css" /> <title>State Pattern</title> <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> </head> <body> <input type="button" value="開始下載" id="download_button" /> <input type="button" value="暫停" id="pause_button" /> <input type="button" value="重新下載" id="resume_button" /> <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> </body> </html>
狀態(tài)模式的使用場景也特別明確,有如下兩點:
參考:https://github.com/tcorral/Design-Patterns-in-Javascript/blob/master/State/1/index.html
更多建議: