關於 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
- 當物件的內部狀態改變時,允許改變其行為
- 物件看起來像改變了類別
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 轉為程式碼
需要判斷狀態來運作的物件
/// <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);
}
}
狀態的介面
/// <summary>
/// 狀態的介面
/// </summary>
public interface IState
{
void Handle(Context context);
}
狀態實做
/// <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();
}
}
- 建立
context
並給定初始狀態 - 依據當前狀態呼叫狀態實做
static void Main(string[] args)
{
Default.Context context = new Default.Context(new Default.ConcreteStateA());
context.Request();
context.Request();
context.Request();
context.Request();
Console.ReadLine();
}
執行結果
State: ConcreteStateA
State: ConcreteStateB
State: ConcreteStateA
State: ConcreteStateB
State: ConcreteStateA
4. 情境
我們接到了一個餐飲部麵包店看板要隨著時間改變內容的需求
- 早上十點到下午兩點「麵包出爐」
- 下午兩點到八點「麵包搭配咖啡打八折」
- 晚上八點到九點「麵包全面七折」
- 其餘時間不營業
狀態的介面
/// <summary>
/// 狀態的介面
/// </summary>
public interface IState
{
void Handle(BreadStore breadStore, int time);
}
狀態實做,處理每個時間區間狀態
/// <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);
}
}
- 建立
breadStore
並給定初始狀態 - 依據當前狀態呼叫狀態實做
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();
}
執行結果
現在時間:9 點, 還沒營業喔~
現在時間:10 點, 麵包出爐囉~
現在時間:13 點, 麵包出爐囉~
現在時間:14 點, 麵包搭配咖啡打八折喔~
現在時間:20 點, 麵包全面七折喔~
完整程式碼
GitHub:Behavioral_08_State