設(shè)計(jì)模式詳解——狀態(tài)模式

前言
今天我們來(lái)看一個(gè)號(hào)稱策略模式雙胞胎的設(shè)計(jì)模式——狀態(tài)模式,如它的名字一樣,狀態(tài)模式最核心的設(shè)計(jì)思路就是將對(duì)象的狀態(tài)抽象出一個(gè)接口,然后根據(jù)它的不同狀態(tài)封裝其行為,這樣就可以實(shí)現(xiàn)狀態(tài)和行為的綁定,最終實(shí)現(xiàn)對(duì)象和狀態(tài)的有效解耦。下面我們就來(lái)詳細(xì)看下它的基本原理和實(shí)現(xiàn)過(guò)程吧。
狀態(tài)模式
狀態(tài)模式允許對(duì)象在內(nèi)部狀態(tài)改變時(shí)改變它的行為,對(duì)象看起來(lái)好像修改了它的類。

要點(diǎn)
狀態(tài)模式允許一個(gè)對(duì)象基于內(nèi)部狀態(tài)而擁有不同的行為 和程序狀態(tài)機(jī)( PSM)不同,狀態(tài)模式用類代表狀態(tài)Context會(huì)將行為委托給當(dāng)前狀態(tài)對(duì)象通過(guò)將每個(gè)狀態(tài)封裝進(jìn)一個(gè)類,我們把以后需要做的任何改變局部化了 狀態(tài)模式和策略模式有相同的類圖,但是它們的意圖不同 策略模式通常會(huì)用行為或算法來(lái)配置 Context類狀態(tài)模式允許 Context隨著狀態(tài)的改變而改變行為狀態(tài)轉(zhuǎn)換可以由 State類或Context類控制使用狀態(tài)模式通常會(huì)導(dǎo)致設(shè)計(jì)中類的數(shù)目大量增加 狀態(tài)類可以被多個(gè) Context示例共享
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
封裝了轉(zhuǎn)換規(guī)則。
枚舉可能的狀態(tài),在枚舉狀態(tài)之前需要確定狀態(tài)種類。
將所有與某個(gè)狀態(tài)有關(guān)的行為放到一個(gè)類中,并且可以方便地增加新的狀態(tài),只需要改變對(duì)象狀態(tài)即可改變對(duì)象的行為。
允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對(duì)象合成一體,而不是某一個(gè)巨大的條件語(yǔ)句塊
可以讓多個(gè)環(huán)境對(duì)象共享一個(gè)狀態(tài)對(duì)象,從而減少系統(tǒng)中對(duì)象的個(gè)數(shù)。
缺點(diǎn)
狀態(tài)模式的使用必然會(huì)增加系統(tǒng)類和對(duì)象的個(gè)數(shù)。 狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)都較為復(fù)雜,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。 狀態(tài)模式對(duì)"開閉原則"的支持并不太好,對(duì)于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無(wú)法切換到新增狀態(tài),而且修改某個(gè)狀態(tài)類的行為也需修改對(duì)應(yīng)類的源代碼。
使用場(chǎng)景
行為隨狀態(tài)改變而改變的場(chǎng)景。 條件、分支語(yǔ)句的代替者。
示例
狀態(tài)接口
首先是狀態(tài)接口,這個(gè)接口是給我們實(shí)際的狀態(tài)對(duì)象繼承的,這個(gè)接口有一個(gè)方法doAction,這個(gè)方法就是給不同的狀態(tài)對(duì)象實(shí)現(xiàn)的,用于處理不同狀態(tài)下的行為的。
public?interface?State?{
????/**
?????*?改變狀態(tài)的操作
?????*?@param?context
?????*/
????void?doAction(Context?context);
}
狀態(tài)所屬者
然后是我們的狀態(tài)所屬者,這個(gè)類有一個(gè)核心的屬性就是我們的State接口。
public?class?Context?{
????private?State?state;
????public?Context(){}
????public?void?setState(State?state){
????????this.state?=?state;
????}
????public?State?getState(){
????????return?state;
????}
?????@Override
????public?String?toString()?{
????????return?"Context{"?+
????????????????"state="?+?state?+
????????????????'}';
????}
}
狀態(tài)實(shí)現(xiàn)
狀態(tài)實(shí)現(xiàn)者繼承了State接口,并實(shí)現(xiàn)了doAction方法,在方法內(nèi)部可以對(duì)我們的狀態(tài)所有者進(jìn)行對(duì)應(yīng)的操作。
這里是一個(gè)啟動(dòng)狀態(tài):
public?class?StopState?implements?State?{
????private?String?name;
????public?StopState()?{
????????this.name?=?"stop";
????}
????@Override
????public?void?doAction(Context?context)?{
????????System.out.println("Context?is?in?stop?state");
????????context.setState(this);
????????System.out.println(context);
????}
????@Override
????public?String?toString()?{
????????return?"StopState{"?+
????????????????"name='"?+?name?+?'\''?+
????????????????'}';
????}
}
這里是停止?fàn)顟B(tài)
public?class?StartState?implements?State{
????private?String?name;
????public?StartState()?{
????????this.name?=?"start";
????}
????@Override
????public?void?doAction(Context?context)?{
????????System.out.println("Context?is?in?start?state");
????????context.setState(this);
????????System.out.println(context);
????}
????@Override
????public?String?toString()?{
????????return?"StartState{"?+
????????????????"name='"?+?name?+?'\''?+
????????????????'}';
????}
}
測(cè)試代碼
這里分別實(shí)例化了容器和狀態(tài)的示例,然后通過(guò)示例的doAction方法操作容器
@Test
????public?void?testState()?{
????????Context?context?=?new?Context();
????????StartState?startState?=?new?StartState();
????????startState.doAction(context);
????????StopState?stopState?=?new?StopState();
????????stopState.doAction(context);
????}
運(yùn)行結(jié)果
可以看到,狀態(tài)對(duì)象的doAction方法執(zhí)行后,容器對(duì)應(yīng)的狀態(tài)也發(fā)生了改變:

好了,關(guān)于狀態(tài)模式就先說(shuō)這么多,接下來(lái)我們做一個(gè)簡(jiǎn)單的總結(jié)。
總結(jié)
有用過(guò)策略模式或者對(duì)策略模式比較熟悉的小伙伴應(yīng)該發(fā)現(xiàn)了:策略模式其實(shí)和我們今天分析狀態(tài)模式特別像,甚至連架構(gòu)模式都是一樣的,所以這里我們有必要說(shuō)下它們的區(qū)別。
首先是策略模式,它其實(shí)是將不同的算法封裝成不同的策略,然后在具體的策略中實(shí)現(xiàn)具體的行為,但是測(cè)試本身是被動(dòng)被選擇的,容器選擇策略,調(diào)用過(guò)程發(fā)生在容器中,而且策略本身是入?yún)ⅲ?/p>
而我們今天分析的狀態(tài)模式,它是將不同狀態(tài)對(duì)應(yīng)的行為封裝,然后由具體的狀態(tài)操作容器,整個(gè)過(guò)程更像是狀態(tài)主動(dòng)發(fā)起的,由狀態(tài)執(zhí)行其自己的方法,入?yún)⑹侨萜鳌?/p>
這兩種設(shè)計(jì)模式從某種程度上說(shuō)是可以互相替換的,但是還是要結(jié)合具體業(yè)務(wù)分析的,比如spring boot啟動(dòng)過(guò)程中,它用到的就是狀態(tài)模式,這一點(diǎn)我們?cè)诜治?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">spring boot啟動(dòng)過(guò)程中也發(fā)現(xiàn)了;但如果是涉及到算法層面的內(nèi)容,比如兩個(gè)數(shù)的加減乘除,顯然策略模式才是更好的選擇。
總之,學(xué)習(xí)設(shè)計(jì)模式除了要了解它的基本原理和應(yīng)用場(chǎng)景之外,更重要的是,要學(xué)會(huì)辨識(shí)優(yōu)秀框架中的設(shè)計(jì)模式(知識(shí),知就是知道,了解,識(shí)就是辨識(shí),分析),最終將這些設(shè)計(jì)模式應(yīng)用到我們的業(yè)務(wù)開發(fā)之中。
- END -