當一個物件的行為取決於其內部狀態,且必須在運行時根據狀態改變其行為時,傳統做法是在方法中使用大量 if-else 或 switch 判斷狀態,導致程式碼龐大、難以維護且違反開閉原則。狀態模式將每種狀態相關的行為封裝進獨立的狀態類別中,讓物件將行為委派給當前狀態物件,實現「物件看起來似乎修改了它所屬的類」。本文透過訊息系統根據用戶權限自動切換儲存策略(XML→SQL)完整展示狀態模式實作。
Message 類別,維護當前狀態參考 _abstractMessageState,提供 Get()、Insert() 對外介面,內部委派給狀態物件,並在操作前呼叫 StateChangeCheck() 檢查狀態轉換。AbstractMessageState,定義所有狀態共同介面:Get()、Insert()、StateChangeCheck(),並持有 Context 參考以便存取用戶資訊與切換狀態。XmlMessage(預設狀態)、SqlMessage(管理員狀態),各自實作具體儲存邏輯與狀態轉換條件。
public class Message
{
private AbstractMessageState _abstractMessageState;
public AbstractMessageState AbstractMessageState
{
get { return _abstractMessageState; }
set { _abstractMessageState = value; }
}
private string _userId;
public string UserId { get { return _userId; } set { _userId = value; } }
public Message(string userId)
{
this._userId = userId;
_abstractMessageState = new XmlMessage(this); // 初始狀態
}
public List<MessageModel> Get()
{
_abstractMessageState.StateChangeCheck(); // 檢查狀態轉換
return _abstractMessageState.Get(); // 委派給當前狀態
}
public bool Insert(MessageModel mm)
{
_abstractMessageState.StateChangeCheck();
return _abstractMessageState.Insert(mm);
}
}
關鍵點:
XmlMessage)。AbstractMessageState 屬性,允許狀態物件主動切換 Context 的狀態。StateChangeCheck(),實現「狀態檢查→行為委派」流程。public abstract class AbstractMessageState
{
protected Message _message;
public Message Message
{
get { return _message; }
set { _message = value; }
}
public abstract void StateChangeCheck();
public abstract List<MessageModel> Get();
public abstract bool Insert(MessageModel mm);
}
持有 Context 參考是狀態模式的關鍵:狀態物件可透過它存取 Context 資料(如 UserId)並修改 Context 狀態(_message.AbstractMessageState = new SqlMessage(...))。
XmlMessage(預設狀態):檢查若用戶為 admin 則切換至 SqlMessage:
public override void StateChangeCheck()
{
if (this._message.UserId == "admin")
{
this._message.AbstractMessageState = new SqlMessage(this._message);
}
}
public override List<MessageModel> Get()
{
var l = new List<MessageModel>();
l.Add(new MessageModel("XML方式獲取Message", DateTime.Now));
return l;
}
SqlMessage(管理員狀態):StateChangeCheck() 為空,表示無需轉換(或可加入降級邏輯)。
Message m = new Message("admin"); // 建立時為 XmlMessage 狀態
m.Insert(...); // 操作前檢查狀態 → 發現 admin → 切換至 SqlMessage → 執行 SQL 插入
m.Get(); // 已為 SqlMessage 狀態 → 直接執行 SQL 查詢
m = new Message("user"); // 一般用戶
m.Get(); // 維持 XmlMessage 狀態 → 執行 XML 查詢
True
SQL方式獲取Message 2007-3-6 22:58:17 ← admin 觸發狀態轉換
True
XML方式獲取Message 2007-3-6 22:58:17 ← user 維持原狀態
同一個 Message 物件,因內部狀態(用戶權限)不同,自動呈現完全不同的行為(SQL vs XML),且狀態轉換邏輯封裝在狀態類別內部,Context 完全不知情。
| 維度 | 策略模式 | 狀態模式 |
|---|---|---|
| 驅動變化 | 客戶端主動選擇 | 物件內部狀態自動觸發 |
| 狀態/策略關係 | 通常獨立、可互換 | 可能有轉換邏輯、生命週期相依 |
| 誰持有參考 | Context 持有 Strategy | State 持有 Context(雙向) |
| 切換時機 | 客戶端決定 | State 內部邏輯決定 |
狀態模式將「狀態判斷與行為執行」從 Context 中剝離,封裝至獨立狀態類別,實現物件行為隨內部狀態動態變化。本範例以訊息系統為例,展示如何根據用戶權限自動在 XML 與 SQL 儲存策略間切換,完整呈現了 Context、State、ConcreteState 三角色協作與雙向參考機制。掌握狀態模式,能讓你在面對複雜狀態驅動業務邏輯時,設計出結構清晰、易於擴展新狀態與轉換規則的系統架構。