# 21. State ###### tags: `DesignPatterns` ## 關於 State 本篇將討論以下幾個問題 > ### 1. 關於 State > ### 2. UML > ### 3. 將 UML 轉為程式碼 > ### 4. 情境 --- ## 測試環境: >OS:Windows 10 >IDE:Visual Studio 2019 --- ## 1. 關於 State > Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. > > by [Gang of Four](https://en.wikipedia.org/wiki/Design_Patterns) - 當物件的內部狀態改變時,允許改變其行為 - 物件看起來像改變了類別 State(狀態)屬於行為型(Behavioral Patterns),當遇到**大量 if/else 或 switch** 時,可藉由 State 來將判斷邏輯抽離原本物件,並將每個狀態邏輯拆分開來,各自處理單一狀態與邏輯,降低物件本身複雜度。 優點: - 符合 單一職責原則(Single Responsibility Principle) - 符合 開閉原則(Open Closed Principle) 缺點: - 隨著狀態數量提高,class 數量增加會造成整體程式複雜度提升 --- ## 2. UML ![](https://i.imgur.com/kc4otJ7.jpg) Class 間關聯: - Context 可包含 State - ConcreteState A/B 繼承 State Class: - Context:需要判斷狀態來運作的物件 - State:狀態的抽象類別或介面 - ConcreteState:狀態實做 --- ## 3. 將 UML 轉為程式碼 需要判斷狀態來運作的物件 ```C# /// <summary> /// 需要判斷狀態來運作的物件 /// </summary> public class Context { private IState _state; public Context(IState state) { State = state; } public IState State { get => _state; set { _state = value; Console.WriteLine($"State: {_state.GetType().Name}"); } } public void Request() { _state.Handle(this); } } ``` 狀態的介面 ```C# /// <summary> /// 狀態的介面 /// </summary> public interface IState { void Handle(Context context); } ``` 狀態實做 ```C# /// <summary> /// 狀態實做 A /// </summary> public class ConcreteStateA : IState { public void Handle(Context context) { context.State = new ConcreteStateB(); } } /// <summary> /// 狀態實做 B /// </summary> public class ConcreteStateB : IState { public void Handle(Context context) { context.State = new ConcreteStateA(); } } ``` 1. 建立`context`並給定初始狀態 2. 依據當前狀態呼叫狀態實做 ```C# static void Main(string[] args) { Default.Context context = new Default.Context(new Default.ConcreteStateA()); context.Request(); context.Request(); context.Request(); context.Request(); Console.ReadLine(); } ``` 執行結果 ```Console State: ConcreteStateA State: ConcreteStateB State: ConcreteStateA State: ConcreteStateB State: ConcreteStateA ``` --- ## 4. 情境 我們接到了一個餐飲部麵包店看板要隨著時間改變內容的需求 - 早上十點到下午兩點「麵包出爐」 - 下午兩點到八點「麵包搭配咖啡打八折」 - 晚上八點到九點「麵包全面七折」 - 其餘時間不營業 狀態的介面 ```C# /// <summary> /// 狀態的介面 /// </summary> public interface IState { void Handle(BreadStore breadStore, int time); } ``` 狀態實做,處理每個時間區間狀態 ```C# /// <summary> /// 還沒營業喔 /// </summary> public class Close : IState { public void Handle(BreadStore breadStore, int time) { if (time < 10 || time > 21) { Console.WriteLine($"現在時間:{time} 點, 還沒營業喔~"); } else { breadStore.State = new FreshBread(); breadStore.Request(time); } } } /// <summary> /// 麵包出爐囉 /// </summary> public class FreshBread : IState { public void Handle(BreadStore breadStore, int time) { if (time >= 10 && time < 14) { Console.WriteLine($"現在時間:{time} 點, 麵包出爐囉~"); } else { breadStore.State = new BreadAndCoffee(); breadStore.Request(time); } } } /// <summary> /// 麵包搭配咖啡打八折 /// </summary> public class BreadAndCoffee : IState { public void Handle(BreadStore breadStore, int time) { if (time >= 14 && time < 20) { Console.WriteLine($"現在時間:{time} 點, 麵包搭配咖啡打八折喔~"); } else { breadStore.State = new ClearingSale(); breadStore.Request(time); } } } /// <summary> /// 麵包全面七折喔 /// </summary> public class ClearingSale : IState { public void Handle(BreadStore breadStore, int time) { if (time >= 20) { Console.WriteLine($"現在時間:{time} 點, 麵包全面七折喔~"); } else { breadStore.State = new Close(); breadStore.Request(time); } } } ``` 麵包店 ``` /// <summary> /// 麵包店 /// </summary> public class BreadStore { public IState State { get; set; } public BreadStore(IState state) { State = state; } public void Request(int time) { State.Handle(this, time); } } ``` 1. 建立`breadStore`並給定初始狀態 2. 依據當前狀態呼叫狀態實做 ```C# static void Main(string[] args) { Situation.BreadStore breadStore = new Situation.BreadStore(new Situation.Close()); breadStore.Request(9); breadStore.Request(10); breadStore.Request(13); breadStore.Request(14); breadStore.Request(20); Console.ReadLine(); } ``` 執行結果 ```Console 現在時間:9 點, 還沒營業喔~ 現在時間:10 點, 麵包出爐囉~ 現在時間:13 點, 麵包出爐囉~ 現在時間:14 點, 麵包搭配咖啡打八折喔~ 現在時間:20 點, 麵包全面七折喔~ ``` --- ## 完整程式碼 GitHub:[Behavioral_08_State](https://github.com/darionnnnnn/blog/tree/master/Blog/Behavioral_08_State) --- ## 總結 ### 一開始處理到 if/else 或 switch 判斷數量不多時,不用急著套用 State 來抽離判斷部分邏輯,少量的 if/else 或 switch 在維護上還是相對容易的,且未來若是沒有新增判斷的需求,那這部分反而是過度設計了。 --- ## 參考資料 1. [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) 2. [大話設計模式](https://www.tenlong.com.tw/products/9789866761799) 2. [dofactory](https://www.dofactory.com/) 3. [Refactoring.Guru](https://refactoring.guru/) --- ## 新手上路,若有錯誤還請告知,謝謝