鍍金池/ 教程/ HTML/ 設(shè)計(jì)模式之狀態(tài)模式
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級(jí)高手必讀篇)
設(shè)計(jì)模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計(jì)模式之中介者模式
設(shè)計(jì)模式之裝飾者模式
設(shè)計(jì)模式之模板方法
設(shè)計(jì)模式之外觀模式
強(qiáng)大的原型和原型鏈
設(shè)計(jì)模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計(jì)模式之適配器模式
設(shè)計(jì)模式之組合模式
設(shè)計(jì)模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點(diǎn)
求值策略
閉包(Closures)
對(duì)象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對(duì)象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計(jì)模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計(jì)模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計(jì)模式之享元模式
設(shè)計(jì)模式之原型模式
根本沒有“JSON 對(duì)象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對(duì)象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開閉原則 OCP
設(shè)計(jì)模式之橋接模式
設(shè)計(jì)模式之策略模式
設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計(jì)模式之工廠模式

設(shè)計(jì)模式之狀態(tài)模式

介紹

狀態(tài)模式(State)允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變的時(shí)候改變它的行為,對(duì)象看起來似乎修改了它的類。

正文

舉個(gè)例子,就比如我們平時(shí)在下載東西,通常就會(huì)有好幾個(gè)狀態(tài),比如準(zhǔn)備狀態(tài)(ReadyState)、下載狀態(tài)(DownloadingState)、暫停狀態(tài)(DownloadPausedState)、下載完畢狀態(tài)(DownloadedState)、失敗狀態(tài)(DownloadFailedState),也就是說在每個(gè)狀態(tài)都只可以做當(dāng)前狀態(tài)才可以做的事情,而不能做其它狀態(tài)能做的事兒。

由于 State 模式描述了下載(Download)如何在每一種狀態(tài)下表現(xiàn)出不同的行為。這一模式的關(guān)鍵思想就是引入了一個(gè)叫做 State 的抽象類(或 JS 里的函數(shù))來表示下載狀態(tài),State 函數(shù)(作為原型)為每個(gè)狀態(tài)的子類(繼承函數(shù))聲明了一些公共接口。其每個(gè)繼承函數(shù)實(shí)現(xiàn)與特定狀態(tài)相關(guān)的行為,比如 DownloadingState 和 DownloadedState 分別實(shí)現(xiàn)了正在下載和下載完畢的行為。這些行為可以通過 Download 來來維護(hù)。

讓我們來實(shí)現(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)?State 的原型定義了 4 個(gè)方法接口,分別對(duì)應(yīng)著下載(download)、暫停(pause)、失?。╢ail)、結(jié)束(finish)以便子函數(shù)可以重寫。

在編寫子函數(shù)之前,我們先來編寫一個(gè) ReadyState 函數(shù),以便可以將狀態(tài)傳遞給第一個(gè) 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ù)接收了一個(gè) Download 維護(hù)函數(shù)的實(shí)例作為參數(shù),Download 函數(shù)用于控制狀態(tài)的改變和獲?。愃朴谥醒肟刂破鳎屚獠空{(diào)用),ReadyState 重寫了原型的 download 方法,以便開始進(jìn)行下載。我們繼續(xù)來看 Download 函數(shù)的主要功能:

var Download = function () {
    this.oState = new ReadyState(this);
};
Download.prototype.setState = function (oState) {
    this.oState = oState;
};
// 對(duì)外暴露的四個(gè)公共方法,以便外部調(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對(duì)象
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 個(gè)方法,4 個(gè)是對(duì)用于下載狀態(tài)的操作行為,另外 4 個(gè)是用于獲取當(dāng)前四個(gè)不同的狀態(tài),這 4 個(gè)方法都接收 this 作為參數(shù),也就是將 Download 實(shí)例自身作為一個(gè)參數(shù)傳遞給處理該請(qǐng)求的狀態(tài)對(duì)象(ReadyState 以及后面要實(shí)現(xiàn)的繼承函數(shù)),這使得狀態(tài)對(duì)象比必要的時(shí)候可以訪問 oDownlaod。

接下來,繼續(xù)定義 4 個(gè)相關(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 的主要注意事項(xiàng)就是已經(jīng)正在下載的文件,不能再次開始下載了,其它的狀態(tài)都可以連續(xù)進(jìn)行。

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("對(duì)下載完了,還暫停啥?");
};
DownloadedState.prototype.fail = function () {
    throw new Error("都下載成功了,咋會(huì)失敗呢?");
};
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("失敗的下載,肯定也不會(huì)成功!");
};

同理,DownloadFailedState 函數(shù)的失敗狀態(tài),也不能再次失敗,但可以和 finished 以后再次嘗試重新下載。

調(diào)用測(cè)試代碼,就非常簡(jiǎn)單了,我們?cè)?HTML 里演示吧,首先是要了 jquery,然后有 3 個(gè)按鈕分別代表:開始下載、暫停、重新下載。(注意在 Firefox 里用 firebug 查看結(jié)果,因?yàn)橛昧? console.log 方法)。

<html>
<head>
    <link type="text/css" rel="stylesheet"  />
    <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>

總結(jié)

狀態(tài)模式的使用場(chǎng)景也特別明確,有如下兩點(diǎn):

  1. 一個(gè)對(duì)象的行為取決于它的狀態(tài),并且它必須在運(yùn)行時(shí)刻根據(jù)狀態(tài)改變它的行為。
  2. 一個(gè)操作中含有大量的分支語句,而且這些分支語句依賴于該對(duì)象的狀態(tài)。狀態(tài)通常為一個(gè)或多個(gè)枚舉常量的表示。