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

點(diǎn)贊再看管行,養(yǎng)成習(xí)慣她倘,公眾號(hào)搜一搜【一角錢技術(shù)】關(guān)注更多原創(chuàng)技術(shù)文章策添。本文 GitHub org_hejianhui/JavaStudy 已收錄,有我的系列文章逸爵。

前言

23種設(shè)計(jì)模式快速記憶的請(qǐng)看上面第一篇幅疼,本篇和大家一起來(lái)學(xué)習(xí)狀態(tài)模式相關(guān)內(nèi)容。

模式定義

對(duì)有狀態(tài)的對(duì)象堂飞,把復(fù)雜的“判斷邏輯”提取到不同的狀態(tài)對(duì)象中哩簿,允許狀態(tài)對(duì)象在其內(nèi)部狀態(tài)發(fā)生改變時(shí)改變其行為。

狀態(tài)模式把受環(huán)境改變的對(duì)象行為包裝在不同的狀態(tài)對(duì)象里酝静,其意圖是讓一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變的時(shí)候节榜,其行為也隨之改變。現(xiàn)在我們來(lái)分析其基本結(jié)構(gòu)和實(shí)現(xiàn)方法别智。

模版實(shí)現(xiàn)如下

package com.niuh.designpattern.state.v1;

/**
 * <p>
 * 狀態(tài)模式
 * </p>
 */
public class StatePattern {

    public static void main(String[] args) {
        //創(chuàng)建環(huán)境  
        Context context = new Context();
        //處理請(qǐng)求
        context.Handle();
        context.Handle();
        context.Handle();
        context.Handle();
    }
}

//抽象狀態(tài)類
abstract class State {
    public abstract void Handle(Context context);
}

//具體狀態(tài)A類
class ConcreteStateA extends State {
    public void Handle(Context context) {
        System.out.println("當(dāng)前狀態(tài)是 A.");
        context.setState(new ConcreteStateB());
    }
}

//具體狀態(tài)B類
class ConcreteStateB extends State {
    public void Handle(Context context) {
        System.out.println("當(dāng)前狀態(tài)是 B.");
        context.setState(new ConcreteStateA());
    }
}

//環(huán)境類
class Context {
    private State state;

    //定義環(huán)境類的初始狀態(tài)
    public Context() {
        this.state = new ConcreteStateA();
    }

    //設(shè)置新狀態(tài)
    public void setState(State state) {
        this.state = state;
    }

    //讀取狀態(tài)
    public State getState() {
        return (state);
    }

    //對(duì)請(qǐng)求做處理
    public void Handle() {
        state.Handle(this);
    }
}

輸出結(jié)果如下

當(dāng)前狀態(tài)是 A.
當(dāng)前狀態(tài)是 B.
當(dāng)前狀態(tài)是 A.
當(dāng)前狀態(tài)是 B.

解決的問題

對(duì)象的行為依賴于它的狀態(tài)(屬性)宗苍,并且可以根據(jù)它的狀態(tài)改變而改變它的相關(guān)行為。

模式組成

組成(角色) 作用
環(huán)境(Context)角色 也稱為上下文薄榛,它定義了客戶感興趣的接口讳窟,維護(hù)一個(gè)當(dāng)前狀態(tài),并將與狀態(tài)相關(guān)的操作委托給當(dāng)前狀態(tài)對(duì)象來(lái)處理敞恋。
抽象狀態(tài)(State)角色 定義一個(gè)接口丽啡,用以封裝環(huán)境對(duì)象中的特定狀態(tài)所對(duì)應(yīng)的行為。
具體狀態(tài)(Concrete State)角色 實(shí)現(xiàn)抽象狀態(tài)所對(duì)應(yīng)的行為硬猫。

實(shí)例說明

實(shí)例概況

用“狀態(tài)模式”設(shè)計(jì)一個(gè)多線程的狀態(tài)轉(zhuǎn)換程序补箍。

分析:多線程存在 5 種狀態(tài),分別為新建狀態(tài)啸蜜、就緒狀態(tài)坑雅、運(yùn)行狀態(tài)、阻塞狀態(tài)和死亡狀態(tài)衬横,各個(gè)狀態(tài)當(dāng)遇到相關(guān)方法調(diào)用或事件觸發(fā)時(shí)會(huì)轉(zhuǎn)換到其他狀態(tài)裹粤,其狀態(tài)轉(zhuǎn)換規(guī)律如下所示:

現(xiàn)在先定義一個(gè)抽象狀態(tài)類(TheadState),然后為上圖的每個(gè)狀態(tài)設(shè)計(jì)一個(gè)具體狀態(tài)類蜂林,它們是新建狀態(tài)(New)遥诉、就緒狀態(tài)(Runnable )拇泣、運(yùn)行狀態(tài)(Running)、阻塞狀態(tài)(Blocked)和死亡狀態(tài)(Dead)矮锈,每個(gè)狀態(tài)中有觸發(fā)它們轉(zhuǎn)變狀態(tài)的方法挫酿,環(huán)境類(ThreadContext)中先生成一個(gè)初始狀態(tài)(New),并提供相關(guān)觸發(fā)方法愕难,下圖所示是線程狀態(tài)轉(zhuǎn)換程序的結(jié)構(gòu)圖:


使用步驟

步驟1:定義抽象狀態(tài)類:線程狀態(tài)

abstract class ThreadState {
    //狀態(tài)名
    protected String stateName;
}

步驟2: 定義具體的狀態(tài)類

//具體狀態(tài)類:新建狀態(tài)
class New extends ThreadState {
    public New() {
        stateName = "新建狀態(tài)";
        System.out.println("當(dāng)前線程處于:新建狀態(tài).");
    }

    public void start(ThreadContext hj) {
        System.out.print("調(diào)用start()方法-->");
        if (stateName.equals("新建狀態(tài)")) {
            hj.setState(new Runnable());
        } else {
            System.out.println("當(dāng)前線程不是新建狀態(tài),不能調(diào)用start()方法.");
        }
    }
}

//具體狀態(tài)類:就緒狀態(tài)
class Runnable extends ThreadState {
    public Runnable() {
        stateName = "就緒狀態(tài)";
        System.out.println("當(dāng)前線程處于:就緒狀態(tài).");
    }

    public void getCPU(ThreadContext hj) {
        System.out.print("獲得CPU時(shí)間-->");
        if (stateName.equals("就緒狀態(tài)")) {
            hj.setState(new Running());
        } else {
            System.out.println("當(dāng)前線程不是就緒狀態(tài)惫霸,不能獲取CPU.");
        }
    }
}

//具體狀態(tài)類:運(yùn)行狀態(tài)
class Running extends ThreadState {
    public Running() {
        stateName = "運(yùn)行狀態(tài)";
        System.out.println("當(dāng)前線程處于:運(yùn)行狀態(tài).");
    }

    public void suspend(ThreadContext hj) {
        System.out.print("調(diào)用suspend()方法-->");
        if (stateName.equals("運(yùn)行狀態(tài)")) {
            hj.setState(new Blocked());
        } else {
            System.out.println("當(dāng)前線程不是運(yùn)行狀態(tài)猫缭,不能調(diào)用suspend()方法.");
        }
    }

    public void stop(ThreadContext hj) {
        System.out.print("調(diào)用stop()方法-->");
        if (stateName.equals("運(yùn)行狀態(tài)")) {
            hj.setState(new Dead());
        } else {
            System.out.println("當(dāng)前線程不是運(yùn)行狀態(tài),不能調(diào)用stop()方法.");
        }
    }
}

//具體狀態(tài)類:阻塞狀態(tài)
class Blocked extends ThreadState {
    public Blocked() {
        stateName = "阻塞狀態(tài)";
        System.out.println("當(dāng)前線程處于:阻塞狀態(tài).");
    }

    public void resume(ThreadContext hj) {
        System.out.print("調(diào)用resume()方法-->");
        if (stateName.equals("阻塞狀態(tài)")) {
            hj.setState(new Runnable());
        } else {
            System.out.println("當(dāng)前線程不是阻塞狀態(tài)壹店,不能調(diào)用resume()方法.");
        }
    }
}

//具體狀態(tài)類:死亡狀態(tài)
class Dead extends ThreadState {
    public Dead() {
        stateName = "死亡狀態(tài)";
        System.out.println("當(dāng)前線程處于:死亡狀態(tài).");
    }
}

步驟3:定義環(huán)境類

class ThreadContext {
    private ThreadState state;

    ThreadContext() {
        state = new New();
    }

    public void setState(ThreadState state) {
        this.state = state;
    }

    public ThreadState getState() {
        return state;
    }

    public void start() {
        ((New) state).start(this);
    }

    public void getCPU() {
        ((Runnable) state).getCPU(this);
    }

    public void suspend() {
        ((Running) state).suspend(this);
    }

    public void stop() {
        ((Running) state).stop(this);
    }

    public void resume() {
        ((Blocked) state).resume(this);
    }
}

輸出結(jié)果

當(dāng)前線程處于:新建狀態(tài).
調(diào)用start()方法-->當(dāng)前線程處于:就緒狀態(tài).
獲得CPU時(shí)間-->當(dāng)前線程處于:運(yùn)行狀態(tài).
調(diào)用suspend()方法-->當(dāng)前線程處于:阻塞狀態(tài).
調(diào)用resume()方法-->當(dāng)前線程處于:就緒狀態(tài).
獲得CPU時(shí)間-->當(dāng)前線程處于:運(yùn)行狀態(tài).
調(diào)用stop()方法-->當(dāng)前線程處于:死亡狀態(tài).

優(yōu)點(diǎn)

  1. 狀態(tài)模式將與特定狀態(tài)相關(guān)的行為局部化到一個(gè)狀態(tài)中猜丹,并且將不同狀態(tài)的行為分割開來(lái),滿足“單一職責(zé)原則”硅卢。
  2. 減少對(duì)象間的相互依賴射窒。將不同的狀態(tài)引入獨(dú)立的對(duì)象中會(huì)使得狀態(tài)轉(zhuǎn)換變得更加明確,且減少對(duì)象間的相互依賴将塑。
  3. 有利于程序的擴(kuò)展脉顿。通過定義新的子類很容易地增加新的狀態(tài)和轉(zhuǎn)換。

缺點(diǎn)

  1. 狀態(tài)模式的使用必然會(huì)增加系統(tǒng)的類與對(duì)象的個(gè)數(shù)点寥。
  2. 狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)都較為復(fù)雜艾疟,如果使用不當(dāng)會(huì)導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。

應(yīng)用場(chǎng)景

通常在以下情況下可以考慮使用狀態(tài)模式敢辩。

  • 當(dāng)一個(gè)對(duì)象的行為取決于它的狀態(tài)蔽莱,并且它必須在運(yùn)行時(shí)根據(jù)狀態(tài)改變它的行為時(shí),就可以考慮使用狀態(tài)模式戚长。
  • 一個(gè)操作中含有龐大的分支結(jié)構(gòu)盗冷,并且這些分支決定于對(duì)象的狀態(tài)時(shí)。

狀態(tài)模式的擴(kuò)展

在有些情況下同廉,可能有多個(gè)環(huán)境對(duì)象需要共享一組狀態(tài)仪糖,這時(shí)需要引入享元模式,將這些具體狀態(tài)對(duì)象放在集合中供程序共享迫肖,其結(jié)構(gòu)圖如下:

分析:共享狀態(tài)模式的不同之處是在環(huán)境類中增加了一個(gè) HashMap 來(lái)保存相關(guān)狀態(tài)乓诽,當(dāng)需要某種狀態(tài)時(shí)可以從中獲取,其程序代碼如下:

package com.niuh.designpattern.state.v3;

import java.util.HashMap;

/**
 * <p>
 * 共享狀態(tài)模式
 * </p>
 */
public class FlyweightStatePattern {
    public static void main(String[] args) {
        //創(chuàng)建環(huán)境 
        ShareContext context = new ShareContext();
        //處理請(qǐng)求
        context.Handle();
        context.Handle();
        context.Handle();
        context.Handle();
    }
}

//抽象狀態(tài)類
abstract class ShareState {
    public abstract void Handle(ShareContext context);
}

//具體狀態(tài)1類
class ConcreteState1 extends ShareState {
    public void Handle(ShareContext context) {
        System.out.println("當(dāng)前狀態(tài)是: 狀態(tài)1");
        context.setState(context.getState("2"));
    }
}

//具體狀態(tài)2類
class ConcreteState2 extends ShareState {
    public void Handle(ShareContext context) {
        System.out.println("當(dāng)前狀態(tài)是: 狀態(tài)2");
        context.setState(context.getState("1"));
    }
}

//環(huán)境類
class ShareContext {
    private ShareState state;
    private HashMap<String, ShareState> stateSet = new HashMap<String, ShareState>();

    public ShareContext() {
        state = new ConcreteState1();
        stateSet.put("1", state);
        state = new ConcreteState2();
        stateSet.put("2", state);
        state = getState("1");
    }

    //設(shè)置新狀態(tài)
    public void setState(ShareState state) {
        this.state = state;
    }

    //讀取狀態(tài)
    public ShareState getState(String key) {
        ShareState s = (ShareState) stateSet.get(key);
        return s;
    }

    //對(duì)請(qǐng)求做處理
    public void Handle() {
        state.Handle(this);
    }
}

輸出結(jié)果如下

當(dāng)前狀態(tài)是: 狀態(tài)1
當(dāng)前狀態(tài)是: 狀態(tài)2
當(dāng)前狀態(tài)是: 狀態(tài)1
當(dāng)前狀態(tài)是: 狀態(tài)2

源碼中的應(yīng)用

#JDK中的狀態(tài)模式:
java.util.Iterator
# 通過FacesServlet控制, 行為取決于當(dāng)前JSF生命周期的階段(狀態(tài)
javax.faces.lifecycle.LifeCycle#execute()

PS:以上代碼提交在 Githubhttps://github.com/Niuh-Study/niuh-designpatterns.git

文章持續(xù)更新咒程,可以公眾號(hào)搜一搜「 一角錢技術(shù) 」第一時(shí)間閱讀鸠天, 本文 GitHub org_hejianhui/JavaStudy 已經(jīng)收錄,歡迎 Star帐姻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末稠集,一起剝皮案震驚了整個(gè)濱河市奶段,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剥纷,老刑警劉巖痹籍,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件软驰,死亡現(xiàn)場(chǎng)離奇詭異贺氓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)唉侄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門悠垛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)线定,“玉大人,你說我怎么就攤上這事确买〗锛ィ” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵湾趾,是天一觀的道長(zhǎng)芭商。 經(jīng)常有香客問我,道長(zhǎng)搀缠,這世上最難降的妖魔是什么铛楣? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮艺普,結(jié)果婚禮上蛉艾,老公的妹妹穿的比我還像新娘。我一直安慰自己衷敌,他們只是感情好勿侯,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缴罗,像睡著了一般助琐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上面氓,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天兵钮,我揣著相機(jī)與錄音,去河邊找鬼舌界。 笑死掘譬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的呻拌。 我是一名探鬼主播葱轩,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了靴拱?” 一聲冷哼從身側(cè)響起垃喊,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袜炕,沒想到半個(gè)月后本谜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偎窘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年乌助,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陌知。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡他托,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纵诞,到底是詐尸還是另有隱情,我是刑警寧澤培遵,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布浙芙,位于F島的核電站,受9級(jí)特大地震影響籽腕,放射性物質(zhì)發(fā)生泄漏嗡呼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一皇耗、第九天 我趴在偏房一處隱蔽的房頂上張望南窗。 院中可真熱鬧,春花似錦郎楼、人聲如沸万伤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)敌买。三九已至,卻和暖如春阶界,著一層夾襖步出監(jiān)牢的瞬間虹钮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工膘融, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芙粱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓氧映,卻偏偏與公主長(zhǎng)得像春畔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359