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

狀態(tài)模式(State)

在軟件開發(fā)過程中,應(yīng)用程序中的有些對象可能會根據(jù)不同的情況做出不同的行為,我們把這種對象稱為有狀態(tài)的對象瑰排,而把影響對象行為的一個或多個動態(tài)變化的屬性稱為狀態(tài)。當(dāng)有狀態(tài)的對象與外部事件產(chǎn)生互動時,其內(nèi)部狀態(tài)會發(fā)生改變料仗,從而使得其行為也隨之發(fā)生改變。如人的情緒有高興的時候和傷心的時候伏蚊,不同的情緒有不同的行為立轧,當(dāng)然外界也會影響其情緒變化。

對這種有狀態(tài)的對象編程,傳統(tǒng)的解決方案是:將這些所有可能發(fā)生的情況全都考慮到氛改,然后使用 if-else 語句來做狀態(tài)判斷帐萎,再進(jìn)行不同情況的處理。但當(dāng)對象的狀態(tài)很多時胜卤,程序會變得很復(fù)雜疆导。而且增加新的狀態(tài)要添加新的 if-else 語句,這違背了“開閉原則”瑰艘,不利于程序的擴(kuò)展是鬼。

以上問題如果采用“狀態(tài)模式”就能很好地得到解決。狀態(tài)模式的解決思想是:當(dāng)控制一個對象狀態(tài)轉(zhuǎn)換的條件表達(dá)式過于復(fù)雜時紫新,把相關(guān)“判斷邏輯”提取出來均蜜,放到一系列的狀態(tài)類當(dāng)中,這樣可以把原來復(fù)雜的邏輯判斷簡單化芒率。

狀態(tài)模式的定義與特點(diǎn)

  • 狀態(tài)(State)模式的定義:
    對有狀態(tài)的對象囤耳,把復(fù)雜的“判斷邏輯”提取到不同的狀態(tài)對象中,允許狀態(tài)對象在其
    內(nèi)部狀態(tài)發(fā)生改變時改變其行為偶芍。
  • 狀態(tài)(State)模式的優(yōu)點(diǎn):
    1.狀態(tài)模式將與特定狀態(tài)相關(guān)的行為局部化到一個狀態(tài)中充择,并且將不同狀態(tài)的行為分割開來,滿足“單一職責(zé)原則”匪蟀。
    2.減少對象間的相互依賴椎麦。將不同的狀態(tài)引入獨(dú)立的對象中會使得狀態(tài)轉(zhuǎn)換變得更加明確,且減少對象間的相互依賴材彪。
    3.有利于程序的擴(kuò)展观挎。通過定義新的子類很容易地增加新的狀態(tài)和轉(zhuǎn)換。
  • 狀態(tài)(State)模式的缺點(diǎn):
    1.狀態(tài)模式的使用必然會增加系統(tǒng)的類與對象的個數(shù)段化。
    2.狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)都較為復(fù)雜嘁捷,如果使用不當(dāng)會導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。

狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)

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

1. 模式的結(jié)構(gòu)
狀態(tài)模式包含以下主要角色缓升。

  1. 環(huán)境(Context)角色:也稱為上下文,它定義了客戶感興趣的接口蕴轨,維護(hù)一個當(dāng)前狀態(tài)港谊,并將與狀態(tài)相關(guān)的操作委托給當(dāng)前狀態(tài)對象來處理。
  2. 抽象狀態(tài)(State)角色:定義一個接口尺棋,用以封裝環(huán)境對象中的特定狀態(tài)所對應(yīng)的行為封锉。
  3. 具體狀態(tài)(Concrete State)角色:實(shí)現(xiàn)抽象狀態(tài)所對應(yīng)的行為。
    狀態(tài)模式的結(jié)構(gòu)圖

    2. 模式的實(shí)現(xiàn)
    狀態(tài)模式的實(shí)現(xiàn)代碼如下:
package state;
public class StatePatternClient
{
    public static void main(String[] args)
    {       
        Context context=new Context();    //創(chuàng)建環(huán)境       
        context.Handle();    //處理請求
        context.Handle();
        context.Handle();
        context.Handle();
    }
}
//環(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);
    }
    //對請求做處理
    public void Handle()
    {
        state.Handle(this);
    }
}
//抽象狀態(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());
    }
}

狀態(tài)模式的實(shí)例

用“狀態(tài)模式”設(shè)計(jì)一個學(xué)生成績的狀態(tài)轉(zhuǎn)換程序膘螟。
分析:本實(shí)例包含了“不及格”“中等”和“優(yōu)秀” 3 種狀態(tài)成福,當(dāng)學(xué)生的分?jǐn)?shù)小于 60 分時為“不及格”狀態(tài),當(dāng)分?jǐn)?shù)大于等于 60 分且小于 90 分時為“中等”狀態(tài)荆残,當(dāng)分?jǐn)?shù)大于等于 90 分時為“優(yōu)秀”狀態(tài)奴艾,我們用狀態(tài)模式來實(shí)現(xiàn)這個程序。
首先内斯,定義一個抽象狀態(tài)類(AbstractState)蕴潦,其中包含了環(huán)境屬性、狀態(tài)名屬性和當(dāng)前分?jǐn)?shù)屬性俘闯,以及加減分方法 addScore(intx) 和檢查當(dāng)前狀態(tài)的抽象方法 checkState()潭苞;然后,定義“不及格”狀態(tài)類 LowState真朗、“中等”狀態(tài)類 MiddleState 和“優(yōu)秀”狀態(tài)類 HighState此疹,它們是具體狀態(tài)類,實(shí)現(xiàn) checkState() 方法遮婶,負(fù)責(zé)檢査自己的狀態(tài)蝗碎,并根據(jù)情況轉(zhuǎn)換;最后旗扑,定義環(huán)境類(ScoreContext)蹦骑,其中包含了當(dāng)前狀態(tài)對象和加減分的方法 add(int score),客戶類通過該方法來改變成績狀態(tài)臀防。


學(xué)生成績的狀態(tài)轉(zhuǎn)換程序的結(jié)構(gòu)圖

程序代碼如下:

package state;
public class ScoreStateTest
{
    public static void main(String[] args)
    {
        ScoreContext account=new ScoreContext();
        System.out.println("學(xué)生成績狀態(tài)測試:");
        account.add(30);
        account.add(40);
        account.add(25);
        account.add(-15);
        account.add(-25);
    }
}
//環(huán)境類
class ScoreContext
{
    private AbstractState state;
    ScoreContext()
    {
        state=new LowState(this);
    }
    public void setState(AbstractState state)
    {
        this.state=state;
    }
    public AbstractState getState()
    {
        return state;
    }   
    public void add(int score)
    {
        state.addScore(score);
    }
}
//抽象狀態(tài)類
abstract class AbstractState
{
    protected ScoreContext hj;  //環(huán)境
    protected String stateName; //狀態(tài)名
    protected int score; //分?jǐn)?shù)
    public abstract void checkState(); //檢查當(dāng)前狀態(tài)
    public void addScore(int x)
    {
        score+=x;       
        System.out.print("加上:"+x+"分眠菇,\t當(dāng)前分?jǐn)?shù):"+score );
        checkState();
        System.out.println("分,\t當(dāng)前狀態(tài):"+hj.getState().stateName);
    }   
}
//具體狀態(tài)類:不及格
class LowState extends AbstractState
{
    public LowState(ScoreContext h)
    {
        hj=h;
        stateName="不及格";
        score=0;
    }
    public LowState(AbstractState state)
    {
        hj=state.hj;
        stateName="不及格";
        score=state.score;
    }
    public void checkState()
    {
        if(score>=90)
        {
            hj.setState(new HighState(this));
        }
        else if(score>=60)
        {
            hj.setState(new MiddleState(this));
        }
    }   
}
//具體狀態(tài)類:中等
class MiddleState extends AbstractState
{
    public MiddleState(AbstractState state)
    {
        hj=state.hj;
        stateName="中等";
        score=state.score;
    }
    public void checkState()
    {
        if(score<60)
        {
            hj.setState(new LowState(this));
        }
        else if(score>=90)
        {
            hj.setState(new HighState(this));
        }
    }
}
//具體狀態(tài)類:優(yōu)秀
class HighState extends AbstractState
{
    public HighState(AbstractState state)
    {
        hj=state.hj;
        stateName="優(yōu)秀";
        score=state.score;
    }           
    public void checkState()
    {
        if(score<60)
        {
            hj.setState(new LowState(this));
        }
        else if(score<90)
        {
            hj.setState(new MiddleState(this));
        }
    }
}

程序運(yùn)行結(jié)果如下:

學(xué)生成績狀態(tài)測試:
加上:30分清钥,    當(dāng)前分?jǐn)?shù):30分琼锋,    當(dāng)前狀態(tài):不及格
加上:40分,    當(dāng)前分?jǐn)?shù):70分祟昭,    當(dāng)前狀態(tài):中等
加上:25分缕坎,    當(dāng)前分?jǐn)?shù):95分,    當(dāng)前狀態(tài):優(yōu)秀
加上:-15分篡悟,    當(dāng)前分?jǐn)?shù):80分谜叹,    當(dāng)前狀態(tài):中等
加上:-25分,    當(dāng)前分?jǐn)?shù):55分搬葬,    當(dāng)前狀態(tài):不及格

狀態(tài)模式的應(yīng)用場景

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

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

在有些情況下,可能有多個環(huán)境對象需要共享一組狀態(tài)疾忍,這時需要引入享元模式乔外,將這些具體狀態(tài)對象放在集合中供程序共享,其結(jié)構(gòu)圖如圖下圖所示一罩。


共享狀態(tài)模式的結(jié)構(gòu)圖

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

package state;
import java.util.HashMap;
public class FlyweightStatePattern
{
    public static void main(String[] args)
    {
        ShareContext context=new ShareContext(); //創(chuàng)建環(huán)境       
        context.Handle(); //處理請求
        context.Handle();
        context.Handle();
        context.Handle();
    }
}
//環(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;
    }
    //對請求做處理
    public void Handle()
    {
        state.Handle(this);
    }
}
//抽象狀態(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"));
    }
}

程序運(yùn)行結(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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末聂渊,一起剝皮案震驚了整個濱河市差购,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汉嗽,老刑警劉巖欲逃,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異饼暑,居然都是意外死亡暖夭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門撵孤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迈着,“玉大人,你說我怎么就攤上這事邪码≡2ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵闭专,是天一觀的道長奴潘。 經(jīng)常有香客問我,道長影钉,這世上最難降的妖魔是什么画髓? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮平委,結(jié)果婚禮上奈虾,老公的妹妹穿的比我還像新娘。我一直安慰自己廉赔,他們只是感情好肉微,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜡塌,像睡著了一般碉纳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上馏艾,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天劳曹,我揣著相機(jī)與錄音奴愉,去河邊找鬼。 笑死铁孵,一個胖子當(dāng)著我的面吹牛躁劣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播库菲,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼志膀!你這毒婦竟也來了熙宇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤溉浙,失蹤者是張志新(化名)和其女友劉穎烫止,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戳稽,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馆蠕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了惊奇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片互躬。...
    茶點(diǎn)故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颂郎,靈堂內(nèi)的尸體忽然破棺而出吼渡,到底是詐尸還是另有隱情,我是刑警寧澤乓序,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布寺酪,位于F島的核電站,受9級特大地震影響替劈,放射性物質(zhì)發(fā)生泄漏寄雀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一陨献、第九天 我趴在偏房一處隱蔽的房頂上張望盒犹。 院中可真熱鬧,春花似錦眨业、人聲如沸阿趁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脖阵。三九已至,卻和暖如春墅茉,著一層夾襖步出監(jiān)牢的瞬間命黔,已是汗流浹背呜呐。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悍募,地道東北人蘑辑。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像坠宴,于是被迫代替她去往敵國和親洋魂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容