狀態模式允許一個對象在其內部狀態改變時改變它的行爲。用電梯來舉例,電梯可以認爲具有開門、關門、運行、停止四種狀態,這四種狀態之間的切換具有多種限制,比如在開門狀態下不電梯不能運行,只能轉爲關門狀態;在運行狀態下,電梯只能轉爲停止狀態...
設想一下,如果要常規的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#的工程化實現及擴展》