# 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

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/)
---
## 新手上路,若有錯誤還請告知,謝謝