狀態模式(一)
本人從事儀器儀表行的軟體開發工作,在軟體的業務邏輯中,經常需要去對儀器的執行流程進行控制,一種方法就是開啟一個while迴圈,通過迴圈不斷地去查詢狀態的值,然後在迴圈內部根據狀態值去執行特定的操作。示例程式碼如下:
static void Main(string[] args) { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); Run(StateType.A, cancellationTokenSource.Token);//阻塞了主執行緒,實際可能會開啟執行緒或者非同步去執行 }
private static void Run(StateType state, CancellationToken token)
{
while (token != null && !token.IsCancellationRequested)
{
switch (state)
{
case StateType.A:
HandleWorkA();
state = StateType.B;
break;
case StateType.B:
HandleWorkB();
state = StateType.C;
break;
case StateType.C:
HandleWorkC();
state = StateType.D;
break;
case StateType.D:
HandleWorkD();
state = StateType.E;
break;
case StateType.E:
HandleWorkE();
state = StateType.A;
break;
}
Thread.Sleep(1000);
}
}
private static void HandleWorkE()
{
Console.WriteLine("執行業務邏輯E");
}
private static void HandleWorkD()
{
Console.WriteLine("執行業務邏輯D");
}
private static void HandleWorkC()
{
Console.WriteLine("執行業務邏輯C");
}
private static void HandleWorkB()
{
Console.WriteLine("執行業務邏輯B");
}
private static void HandleWorkA()
{
Console.WriteLine("執行業務邏輯A");
}
}
enum StateType
{
A,
B,
C,
D,
E
}
以上程式碼開啟了一個迴圈,在迴圈中,我們通過switch case 不斷地去判斷state的值,然後處理相應的業務,在業務處理結束時,我們會更新state的值,從而實現流程的跳轉。
觀察上面的程式碼我們可以發現幾個特點:
1、狀態切換的”動力源“是while迴圈。在上述程式碼中,HandleWorkA()、HandleWorkB()等等業務邏輯之所以能夠被執行,狀態之所以能夠切換,其”動力“正是來自於while迴圈。
2、各個case下執行的程式碼邏輯是基本相似的。在每個case下,都會執行各自的業務邏輯,並且會決定下一個迴圈中switch的state值,在本例中就是去執行HandleWorkA()、HandleWorkB()等方法,在執行完這些方法後,狀態state進行了更新。
3、新的狀態,是由老的狀態決定的。在每個case的break之前,其實已經知道下一次迴圈的狀態了。比如在執行case A的業務後,我們就知道,下一個執行的必定是case B的業務。
由此我們想到,如果我們在執行case A的業務之後,直接呼叫執行case B的業務,那不就不需要while迴圈來給狀態之間的跳轉提供”動力“了嗎?那是否在caseA 的HandleWorkA()之後呼叫HandleWorkB()就可以了呢?顯然是不行的,因為如果這樣做,在呼叫完HandleWorkB()我們同樣還需要考慮執行HandleWorkC()的問題,最後程式碼就成了以下這樣:
private staticvoid Run(StateType state, CancellationToken token) { while (token != null && !token.IsCancellationRequested) { switch (state) { case StateType.A: HandleWorkA(); HandleWorkB(); HandleWorkC(); HandleWorkD(); HandleWorkE(); break; } } }
究其原因,是因為我們在執行完當前case的業務之後呼叫新的case的業務時,呼叫的是具體的方法,因此,我們在程式碼中必須呼叫不同的方法。
而上面我們提到”各個case下執行的程式碼邏輯是基本相似的“,因此我們考慮將每個case下的程式碼邏輯進行抽象,從而實現統一的呼叫。由此引出了本文的主題,狀態模式,以上程式碼,使用狀態模式的實現如下:
static void Main(string[] args) { StateMachine stateMachine = new StateMachine(new StateA()); stateMachine.UpdateState(); } interface IState { void Handle(StateMachine stateMachine);//把stateMachine傳到Handle方法裡,因為State業務邏輯可能需要用到stateMachine } class StateMachine { IState state; public StateMachine(IState initState) { this.state = initState; } public void UpdateState() { state.Handle(this); } } class StateA : IState
{
public void Handle(StateMachine stateMachine)
{
Console.WriteLine("執行業務邏輯A");
Thread.Sleep(1000);
new StateB().Handle(stateMachine);
}
}
class StateB : IState
{
public void Handle(StateMachine stateMachine)
{
Console.WriteLine("執行業務邏輯B");
Thread.Sleep(1000);
new StateC().Handle(stateMachine);
}
}
class StateC : IState
{
public void Handle(StateMachine stateMachine)
{
Console.WriteLine("執行業務邏輯C");
Thread.Sleep(1000);
new StateD().Handle(stateMachine);
}
}
class StateD : IState
{
public void Handle(StateMachine stateMachine)
{
Console.WriteLine("執行業務邏輯D");
Thread.Sleep(1000);
new StateE().Handle(stateMachine);
}
}
class StateE : IState
{
public void Handle(StateMachine stateMachine)
{
Console.WriteLine("執行業務邏輯E");
Thread.Sleep(1000);
new StateA().Handle(stateMachine);
}
}
對比兩種方式我們寫的程式碼,我們發現了一些改變:
1、switch case沒有了,取而代之的是多個State,我們將不同狀態要執行的業務邏輯封裝到了StateA-StateE中;
2、while迴圈沒有了,狀態跳轉的實現,是通過在每個狀態的Handle方法結束前執行新狀態的Handle方法實現的,如在StateA的Handle方法末尾,我們執行了new StateB().Handle(stateMachine);
那麼在本例中使用狀態模式的好處是什麼呢?我概括為以下幾個方面:
1、對不同狀態的業務邏輯進行了封裝,程式碼邏輯的封裝性更好
2、去掉了while 和switch case,在新增狀態時,只需要定義新的狀態類就可以,程式碼的主呼叫過程( StateMachine stateMachine = new StateMachine(new StateA()); stateMachine.UpdateState();)不變,因而拓展性更好
3、swit case需要判斷所有可能的狀態,而在State類中,我們只會關注和State相關的狀態,如在StateA中我們只關注StateA執行的業務邏輯DoWorkA,以及它的後續狀態StateB。
本文中stateMachine.UpdateState();在運行了之後,再沒有接受外部的控制,狀態的跳轉是通過狀態的業務邏輯控制的,是自驅動的,那如何讓外部業務邏輯來驅動狀態跳轉呢?這一問題將在下一篇文章中介紹。