當系統需要建立大量相似的細粒度物件(如文字編輯器每個字元、遊戲每顆子彈、圖形編輯器每個圖元)時,每個物件獨立儲存所有狀態會導致記憶體耗盡。享元模式透過「共享技術」有效支援大量細粒度物件:將物件狀態分為「內部狀態」(可共享、不隨環境變化)與「外部狀態」(不可共享、隨環境變化),僅保存一份內部狀態供多個物件共享,外部狀態由客戶端傳入。本文以訊息系統為例,展示享元工廠如何共享 SQL/XML 兩種儲存策略物件。
AbstractMessage,定義 Get()、Insert(MessageModel) 操作介面,MessageModel 為外部狀態傳入參數。SqlMessage、XmlMessage,實作具體儲存邏輯,內部狀態為空(無實例欄位),純行為封裝,可安全共享。MessageFactory,維護享元池 Dictionary<string, AbstractMessage>,提供 GetMessageObject(key) 獲取/建立共享實例。MessageModel。
public abstract class AbstractMessage
{
public abstract List<MessageModel> Get();
public abstract bool Insert(MessageModel mm); // MessageModel 為外部狀態
}
關鍵:方法簽名接收 MessageModel 參數,而非儲存為實例欄位。這使享元實例無狀態、可安全共享。
public class SqlMessage : AbstractMessage
{
public override List<MessageModel> Get()
{
var l = new List<MessageModel>();
l.Add(new MessageModel("SQL方式獲取Message", DateTime.Now));
return l;
}
public override bool Insert(MessageModel mm) { /* SQL 插入 */ return true; }
}
// XmlMessage 結構相同,僅回傳訊息不同
無任何實例欄位,純函數式行為封裝,天生執行緒安全,適合共享。
public class MessageFactory
{
private Dictionary<string, AbstractMessage> _messageObjects = new Dictionary<string, AbstractMessage>();
public AbstractMessage GetMessageObject(string key)
{
AbstractMessage messageObject = null;
if (_messageObjects.ContainsKey(key))
messageObject = _messageObjects[key];
else
{
switch (key)
{
case "xml": messageObject = new SqlMessage(); break; // 注意:原碼此處有誤,xml 對應 SqlMessage
case "sql": messageObject = new XmlMessage(); break; // sql 對應 XmlMessage
}
_messageObjects.Add(key, messageObject);
}
return messageObject;
}
}
關鍵機制:
string[] ary = { "xml", "sql" };
MessageFactory factory = new MessageFactory();
foreach (string key in ary)
{
AbstractMessage msg = factory.GetMessageObject(key); // 獲取共享實例
Response.Write(msg.Insert(new MessageModel("插入", DateTime.Now))); // 傳入外部狀態
Response.Write(msg.Get()[0].Message + " " + msg.Get()[0].PublishTime);
}
True
SQL方式獲取Message 2007-5-17 22:20:38
True
XML方式獲取Message 2007-5-17 22:20:38
結果顯示:兩次請求 "xml" 獲取同一 SqlMessage 實例,兩次請求 "sql" 獲取同一 XmlMessage 實例,驗證共享機制生效。
| 維度 | 內部狀態 | 外部狀態 |
|---|---|---|
| 定義 | 可共享、不隨環境變化 | 不可共享、隨環境變化 |
| 儲存位置 | 享元物件內部欄位 | 客戶端/呼叫者,傳入方法參數 |
| 本範例 | 儲存類型(SQL/XML)、連線字串、快取設定 | MessageModel(訊息內容、時間) |
| 設計原則 | 儘量多、儘量精簡 | 儘量少、由客戶端管理 |
當需要共享的內部狀態組合較複雜時,可建立 CompositeFlyweight 組合多個 ConcreteFlyweight,客戶端視為單一享元使用。
MessageFactory.Instance),確保全域唯一池。Dictionary<string, WeakReference<Flyweight>>,記憶體壓力時允許 GC 回收享元,下次請求重建。ObjectPool<Flyweight> 配合 IDisposable 歸還機制,適用於高頻建立/銷毀場景。string.Intern() 共享相同字串字面值(編譯器自動、執行時手動)。享元模式以「內外部狀態分離 + 工廠池化共享」解決大量細粒度物件的記憶體問題。本範例以 SQL/XML 兩種儲存策略為享元,展示了 MessageFactory 如何透過字典池確保相同 key 回傳同一實例,客戶端操作時傳入 MessageModel 作為外部狀態。實務中,從字串驻留、枚舉實例、WPF Freezable 到編譯器語法樹、資料庫查詢計畫快取,享元模式是高效能系統的基石。掌握其核心在於「識別可共享的內部狀態、剝離外部狀態、設計高效鍵值工廠」,能讓你在面對「百萬級物件、記憶體受限、高併發」的系統挑戰時,從容應對並設計出極致優化的架構。