深入理解Java設計模式之狀態模式

一、什麼是狀態模式

定義:當一個對象的內在狀態改變時允許改變其行為,這個對象看起來像是改變瞭其類。

主要解決:當控制一個對象狀態的條件表達式過於復雜時的情況。把狀態的判斷邏輯轉移到表示不同狀態的一系列類中,可以把復雜的判斷邏輯簡化。

意圖:允許一個對象在其內部狀態改變時改變它的行為

二、狀態模式的結構

在該類圖中,我們看到三個角色:

(1)Context: 環境類,定義客戶感興趣的接口,維護一個State子類的實例,這個實例對應的是對象當前的狀態。

(2)State:抽象狀態類或者狀態接口,定義一個或者一組行為接口,表示該狀態下的行為動作。

(3)ConcreteState: 具體狀態類,實現State抽象類中定義的接口方法,從而達到不同狀態下的不同行為。

三、狀態模式的使用場景

1.一個對象的行為取決於它的狀態,並且它必須在運行時刻根據狀態改變它的行為。

2.一個操作中含有龐大的多分支結構,並且這些分支決定於對象的狀態。

例子:我們在微博上看到一篇文章,覺得還不錯,於是想評論或者轉發,但如果用戶沒有登錄,這個時候就會先自動跳轉到登錄註冊界面,如果已經登錄,當然就可以直接評論或者轉發瞭。這裡我們可以看到,我們用戶的行為是由當前是否登錄這個狀態來決定的,這就是典型的狀態模式情景。

當然還包括很多其他動作,例如轉發、分享、打賞等等,都要重復判斷狀態才行,如果程序隨著需求的改動或者功能邏輯的增加需要修改代碼,那麼你隻要遺漏瞭一個判斷,就會出問題。

而使用狀態模式,可以很好地避免過多的if–else –分支,狀態模式將每一個狀態分支放入一個獨立的類中,每一個狀態對象都可以獨立存在,程序根據不同的狀態使用不同的狀態對象來實現功能。

四、狀態模式和策略模式對比

如果我們在編寫代碼的時候,遇到大量的條件判斷的時候,可能會采用策略模式來優化結構,因為這時涉及到策略的選擇,但有時候仔細查看下,就會發現,這些所謂的策略其實是對象的不同狀態,更加明顯的是,對象的某種狀態也成為判斷的條件。

策略模式的Context含有一個Strategy的引用,將自身的功能委托給Strategy來完成。

我們把Strategy接口改個名字為State,這就是狀態模式瞭,同樣Context也有一個State類型的引用,也將自己的部門功能委托給State來完成。

要使用狀態模式,我們必須明確兩個東西:狀態和每個狀態下執行的動作。

在狀態模式中,因為所有的狀態都要執行相應的動作,所以我們可以考慮將狀態抽象出來。

狀態的抽象一般有兩種形式:接口和抽象類。如果所有的狀態都有共同的數據域,可以使用抽象類,但如果隻是單純的執行動作,就可以使用接口。

他們之間真正的區別在策略模式對Strategy的具體實現類有絕對的控制權,即Context要感知Strategy具體類型。而狀態模式,Context不感知State的具體實現,Context隻需調用自己的方法,這個調用的方法會委托給State來完成,State會在相應的方法調用時,自動為Context設置狀態,而這個過程對Context來說是透明的,不被感知的。

五、狀態模式的優缺點

優點:

1、封裝瞭轉換規則。

2、枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類。

3、將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,隻需要改變對象狀態即可改變對象的行為。

4、允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。

5、可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數。

缺點:

1、狀態模式的使用必然會增加系統類和對象的個數。

2、狀態模式的結構與實現都較為復雜,如果使用不當將導致程序結構和代碼的混亂。

3、狀態模式對“開閉原則”的支持並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法切換到新增狀態;而且修改某個狀態類的行為也需修改對應類的源代碼。

六、狀態模式的實現

抽象狀態類,定義一個接口以封裝與Context類(Work)的一個特定狀態相關的行為。

public abstract class State
{
    public abstract void WriteProgram(Work w);
}

工作狀態類,每一個子類實現一個與Context類(Work)的一個狀態相關的行為。

//上午工作狀態
public class ForeNoonState : State
{
    public override void WriteProgram(Work w)
    {
        if (w.Hour < 12)
        {
            Console.WriteLine("當前時間:{0}點,上午工作,精神百倍", w.Hour);
        }
        else
        {
            //超過12點,則轉入中午工作狀態
            w.SetState(new NoonState());
            w.WriteProgram();
        }
    }
}
//中午工作狀態
public class NoonState : State
{
    public override void WriteProgram(Work w)
    {
        if (w.Hour < 13)
        {
            Console.WriteLine("當前時間:{0}點,吃午飯,午休", w.Hour);
        }
        else
        {
            //超過13點,則轉入下午工作狀態
            w.SetState(new AfterNoonState());
            w.WriteProgram();
        }
    }
}
//下午工作狀態
public class AfterNoonState : State
{
    public override void WriteProgram(Work w)
    {
        if (w.Hour < 17)
        {
            Console.WriteLine("當前時間:{0}點,下午工作,繼續努力", w.Hour);
        }
        else
        {
            //超過17點,則轉入晚上工作狀態
            w.SetState(new EveningState());
            w.WriteProgram();
        }
    }
}
//晚上工作狀態
public class EveningState : State
{
    public override void WriteProgram(Work w)
    {
        if (w.Finish)
        {
            //如果狀態已完成,則轉入下班狀態
            w.SetState(new RestState());
            w.WriteProgram();
        }
        else
        {
            if (w.Hour < 21)
            {
                Console.WriteLine("當前時間:{0}點,加班", w.Hour);
            }
            else
            {
                //超過21點,則轉入睡眠工作狀態
                w.SetState(new SleepingState());
                w.WriteProgram();
            }
        }
    }
}
//睡眠工作狀態
public class SleepingState : State
{
    public override void WriteProgram(Work w)
    {
        Console.WriteLine("當前時間:{0}點,睡覺", w.Hour);
    }
}
//下班休息狀態
public class RestState : State
{
    public override void WriteProgram(Work w)
    {
        Console.WriteLine("當前時間:{0}點,下班", w.Hour);
    }
}

工作類,Context類,維護一個ConcreteState子類(工作狀態類)的實例,這個實例定義當前的狀態

public class Work
{
    private State current;
    public Work()
    {
        //工作初始化為上午工作狀態
        current = new ForeNoonState();
    }
    //“鐘點”屬性,狀態轉換的依據
    private double hour;
    public double Hour
    {
        get { return hour; }
        set { hour = value; }
    }
    //“任務完成”屬性,是否能下班的依據
    private bool finish = false;
    public bool Finish
    {
        get { return finish; }
        set { finish = value; }
    }
     public void SetState(State s)
    {
        current = s;
    }
     public void WriteProgram()
    {
        current.WriteProgram(this);
    }
}

客戶端代碼

class Program
{
    //客戶端代碼
    static void Main(string[] args)
    {
        Work w = new Work();
        w.Hour = 9;
        w.WriteProgram();
        w.Hour = 10;
        w.WriteProgram();
        w.Hour = 12;
        w.WriteProgram();
        w.Hour = 13;
        w.WriteProgram();
        w.Hour = 14;
        w.WriteProgram();
        w.Hour = 17;
         w.Finish = false;
        //w.Finish = true;
        w.WriteProgram();
        w.Hour = 19;
        w.WriteProgram();
        w.Hour = 22;
        w.WriteProgram();
         Console.Read();
    }
}

結果

當前時間:9點,上午工作,精神百倍
當前時間:10點,上午工作,精神百倍
當前時間:12點,吃午飯,午休
當前時間:13點,下午工作,繼續努力
當前時間:14點,下午工作,繼續努力
當前時間:17點,加班
當前時間:19點,加班
當前時間:22點,睡覺

七、總結

在對象的行動取決於本身的狀態時,可以適用於狀態模式,免去瞭過多的if–else判斷,這對於一些復雜的和繁瑣的判斷邏輯有很好的幫助。但是使用狀態模式,勢必會造成更多的接口和類,對於非常簡單的狀態判斷,可以不使用。

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: