設計模式(21) 狀態模式

狀態模式允許一個對象在其內部狀態改變時改變它的行爲。用電梯來舉例,電梯可以認爲具有開門、關門、運行、停止四種狀態,這四種狀態之間的切換具有多種限制,比如在開門狀態下不電梯不能運行,只能轉爲關門狀態;在運行狀態下,電梯只能轉爲停止狀態...
設想一下,如果要常規的if-else或者switch-case描述電梯的這幾種狀態間的切換,將生成非常複雜的、邏輯相互交織的代碼,可讀性差且不易維護。

而如果用狀態模式來實現,會是怎樣的呢?
首先創建LiftState,代表抽象的電梯狀態,包含了電梯的四個動作(方法),通過這些方法可以切換到對應的狀態。

public abstract class LiftState
{
    protected Context context;
    public void SetContext(Context context)
    {
        this.context = context;
    }

    public abstract void Open();
    public abstract void Close();
    public abstract void Run();
    public abstract void Stop();
}

Context是上下文類,它的作用是串聯各個狀態的過渡,在LiftSate抽象類中把Context類角色聚合進來,並傳遞到子類,這樣4個具體的實現類中自己根據環境來決定如何進行狀態的過渡。

public class Context
{
    public readonly static OpenningState openningState = new OpenningState();
    public readonly static ClosingState closingState = new ClosingState();
    public readonly static RunningState runningState = new RunningState();
    public readonly static StoppingState stoppingState = new StoppingState();

    private LiftState liftState;
    public LiftState LiftState
    {
        get
        {
            return liftState;
        }
        set
        {
            liftState = value;
            liftState.SetContext(this);
        }
    }

    public void Open()
    {
        this.liftState.Open();
    }
    public void Close()
    {
        this.liftState.Close();
    }
    public void Run()
    {
        this.liftState.Run();
    }
    public void Stop()
    {
        this.liftState.Stop();
    }
}

接下來是四個具體的狀態類,負責狀態之間的切換和控制,以OpenningState爲例,只能切換到Closing狀態,其它切換狀態的方法都是空實現。


public class OpenningState : LiftState
{
    public override void Close()
    {
        base.context.LiftState = Context.closingState;
        base.context.LiftState.Close();
    }

    public override void Open()
    {
        Console.WriteLine("Openning");
    }

    public override void Run()
    {
        //
    }

    public override void Stop()
    {
        //
    }
}
public class ClosingState : LiftState
{
    public override void Close()
    {
        Console.WriteLine("Closing");
    }

    public override void Open()
    {
        base.context.LiftState = Context.openningState;
        base.context.LiftState.Open();
    }

    public override void Run()
    {
        base.context.LiftState = Context.runningState;
        base.context.LiftState.Run();
    }

    public override void Stop()
    {
        base.context.LiftState = Context.stoppingState;
        base.context.LiftState.Stop();
    }
}

public class RunningState : LiftState
{
    public override void Close()
    {
//
    }

    public override void Open()
    {
//
    }

    public override void Run()
    {
        Console.WriteLine("Running");
    }

    public override void Stop()
    {
        base.context.LiftState = Context.stoppingState;
        base.context.LiftState.Stop();
    }
}
public class StoppingState : LiftState
{
    public override void Close()
    {
        //
    }

    public override void Open()
    {
        base.context.LiftState = Context.openningState;
        base.context.LiftState.Open();
    }

    public override void Run()
    {
        base.context.LiftState = Context.runningState;
        base.context.LiftState.Run();
    }

    public override void Stop()
    {
        Console.WriteLine("Stopping");
    }
}

狀態模式

通過上面的例子可以直觀得看到狀態模式的特點,它的核心是封裝,狀態的變更引起了行爲的變更,從外部看起來就好像這個對象對應的類發生了改變一樣。
GOF對狀態模式的描述爲:
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
— Design Patterns : Elements of Reusable Object-Oriented Software

狀態模式的UML類圖爲

狀態模式中有3個角色:

  • State(抽象狀態角色),接口或抽象類,負責對象狀態定義,並且封裝環境角色以實現狀態切換。
  • ConcreteState(具體狀態角色),每一個具體狀態必須完成兩個職責:就是本狀態下要做的事情,以及本狀態如何過渡到其他狀態。
  • Context(環境角色),定義客戶端需要的接口,並且負責具體狀態的切換。

狀態模式的通用的代碼

public abstract class State
{
    protected Context context;

    public void SetState(Context context)
    {
        this.context = context;
    }

    public abstract void Handle1();
    public abstract void Handle2();
}

public class ConcreteState1 : State
{
    public override void Handle1()
    {
        //本狀態下必須處理的邏輯
    }

    public override void Handle2()
    {
        base.context.CurrentState = Context.STATE2;
        base.context.Handle2();
    }
}

public class ConcreteState2 : State
{
    public override void Handle1()
    {
        base.context.CurrentState = Context.STATE1;
        base.context.Handle1();
    }

    public override void Handle2()
    {
        //本狀態下必須處理的邏輯
    }
}

public class Context
{
    public readonly static State STATE1 = new ConcreteState1();
    public readonly static State STATE2 = new ConcreteState2();

    private State currentState;
    public State CurrentState
    {
        get
        {
            return currentState;
        }
        set
        {
            this.currentState = value;
            this.currentState.SetState(this);
        }
    }

    public void Handle1()
    {
        this.CurrentState.Handle1();
    }

    public void Handle2()
    {
        this.CurrentState.Handle2();
    }
}

關於Context類,通常的做法是把狀態對象聲明爲靜態常量,有幾個狀態對象就聲明幾個靜態常量。而且環境角色具有狀態抽象角色定義的所有行爲,具體執行使用委託方式。

調用端代碼:

public class Test
{
    public static void Entry()
    {
        Context context = new Context();
        context.CurrentState = Context.STATE1;
        context.Handle1();
        context.Handle2();
    }
}

狀態模式的優缺點

優點

  • 結構清晰,避免了過多的switch...case或者if...else語句的使用,降低了程序的複雜性,提高系統的可維護性。
  • 遵循設計原則,很好地體現了開閉原則和單一職責原則,每個狀態都是一個子類,增加狀態就要增加子類,修改狀態則只需要修改對應的子類。
  • 封裝性非常好,這也是狀態模式的基本要求,狀態變換放置到類的內部來實現,外部的調用不用知道類內部如何實現狀態和行爲的變換。

缺點
狀態模式主要的缺點在於,隨着狀態的增加,子類會變得太多。

狀態模式的適用場景

  • 行爲需要隨狀態的改變而改變時
  • 業務邏輯比較複雜,導致程序中大量使用了switch或者if語句,爲了避免程序結構不清晰,邏輯混亂,可以使用狀態模式來重構,通過擴展子類來實現了條件的判斷處理。
  • 另外,使用整體模式也需要注意避免濫用,只有當某個對象在它的狀態發生改變時,它的行爲也隨着發生比較大的變化時,才考慮用狀態模式,而且對象的狀態最好不要超過5個。

參考書籍:
王翔著 《設計模式——基於C#的工程化實現及擴展》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章