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

2018-02-24 15:25 更新

介紹

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

正文

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

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

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

在編寫(xiě)子函數(shù)之前,我們先來(lái)編寫(xiě)一個(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以后,可以開(kāi)始下載,所以設(shè)置了Download函數(shù)里的狀態(tài)獲取方法
 console.log("Start Download!");
};

ReadyState.prototype.pause = function () {
    throw new Error("還沒(méi)開(kāi)始下載,不能暫停!");
};

ReadyState.prototype.fail = function () {
    throw new Error("文件還沒(méi)開(kāi)始下載,怎么能說(shuō)失敗呢!");
};

ReadyState.prototype.finish = function () {
    throw new Error("文件還沒(méi)開(kāi)始下載,當(dāng)然也不能結(jié)束了!");
};

該函數(shù)接收了一個(gè)Download維護(hù)函數(shù)的實(shí)例作為參數(shù),Download函數(shù)用于控制狀態(tài)的改變和獲?。愃朴谥醒肟刂破?,讓外部調(diào)用),ReadyState重寫(xiě)了原型的download方法,以便開(kāi)始進(jìn)行下載。我們繼續(xù)來(lái)看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í)候可以訪問(wèn)oDownlaod。

接下來(lái),繼續(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)正在下載的文件,不能再次開(kāi)始下載了,其它的狀態(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è)贖TML里演示吧,首先是要了jquery,然后有3個(gè)按鈕分別代表:開(kāi)始下載、暫停、重新下載。(注意在Firefox里用firebug查看結(jié)果,因?yàn)橛昧?console.log方法)。

<html>
<head>
    <link type="text/css" rel="stylesheet"  rel="external nofollow" target="_blank"  />
    <title>State Pattern</title>
    <script type="text/javascript" src="https://atts.w3cschool.cn/attachments/image/cimg/jquery.js"></script>
    <script type="text/javascript" src="Download.js"></script>
    <script type="text/javascript" src="https://atts.w3cschool.cn/attachments/image/cimg/State.js"></script>
    <script type="text/javascript" src="https://atts.w3cschool.cn/attachments/image/cimg/DownloadFailedState.js"></script>
    <script type="text/javascript" src="https://atts.w3cschool.cn/attachments/image/cimg/DownloadPausedState.js"></script>
    <script type="text/javascript" src="https://atts.w3cschool.cn/attachments/image/cimg/DownloadedState.js"></script>
    <script type="text/javascript" src="https://atts.w3cschool.cn/attachments/image/cimg/DownloadingState.js"></script>
    <script type="text/javascript" src="https://atts.w3cschool.cn/attachments/image/cimg/ReadyState.js"></script>
</head>
<body>
    <input type="button" value="開(kāi)始下載" 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è)操作中含有大量的分支語(yǔ)句,而且這些分支語(yǔ)句依賴于該對(duì)象的狀態(tài)。狀態(tài)通常為一個(gè)或多個(gè)枚舉常量的表示。

參考:https://github.com/tcorral/Design-Patterns-in-Javascript/blob/master/State/1/index.html

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)