設計模式(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#的工程化實現及擴充套件》