首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 享元模式深度解析:大量細粒度物件的記憶體優化藝術

     編輯:關於C#

前言

當系統需要建立大量相似的細粒度物件(如文字編輯器每個字元、遊戲每顆子彈、圖形編輯器每個圖元)時,每個物件獨立儲存所有狀態會導致記憶體耗盡。享元模式透過「共享技術」有效支援大量細粒度物件:將物件狀態分為「內部狀態」(可共享、不隨環境變化)與「外部狀態」(不可共享、隨環境變化),僅保存一份內部狀態供多個物件共享,外部狀態由客戶端傳入。本文以訊息系統為例,展示享元工廠如何共享 SQL/XML 兩種儲存策略物件。

模式結構與四大角色

  • Flyweight(抽象享元):AbstractMessage,定義 Get()、Insert(MessageModel) 操作介面,MessageModel 為外部狀態傳入參數。
  • ConcreteFlyweight(具體享元):SqlMessage、XmlMessage,實作具體儲存邏輯,內部狀態為空(無實例欄位),純行為封裝,可安全共享。
  • FlyweightFactory(享元工廠):MessageFactory,維護享元池 Dictionary<string, AbstractMessage>,提供 GetMessageObject(key) 獲取/建立共享實例。
  • Client(客戶端):向工廠請求享元,操作時傳入外部狀態 MessageModel。
享元模式類別圖

核心程式碼機制深度解析

1. 抽象享元:外部狀態參數化

public abstract class AbstractMessage
{
    public abstract List<MessageModel> Get();
    public abstract bool Insert(MessageModel mm); // MessageModel 為外部狀態
}

關鍵:方法簽名接收 MessageModel 參數,而非儲存為實例欄位。這使享元實例無狀態、可安全共享。

2. 具體享元:無狀態行為封裝

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 結構相同,僅回傳訊息不同

無任何實例欄位,純函數式行為封裝,天生執行緒安全,適合共享。

3. 享元工廠:池化管理與鍵值查找

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;
    }
}

關鍵機制:

  • 字典快取已建立的享元實例,確保相同 key 回傳同一實例(單例模式變體)。
  • Key 設計對應內部狀態維度:此處 key 區分「儲存類型」(SQL/XML)。
  • 工廠封裝建立邏輯,客戶端不知曉共享機制。

4. 客戶端:請求享元與傳入外部狀態


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 實例,驗證共享機制生效。

享元模式核心概念:內部狀態 vs 外部狀態

維度內部狀態外部狀態
定義可共享、不隨環境變化不可共享、隨環境變化
儲存位置享元物件內部欄位客戶端/呼叫者,傳入方法參數
本範例儲存類型(SQL/XML)、連線字串、快取設定MessageModel(訊息內容、時間)
設計原則儘量多、儘量精簡儘量少、由客戶端管理

進階應用與最佳實務

複合享元:組合多個純享元

當需要共享的內部狀態組合較複雜時,可建立 CompositeFlyweight 組合多個 ConcreteFlyweight,客戶端視為單一享元使用。

享元與工廠/單例模式結合

  • 享元工廠本質上是「參數化單例」:每個 key 對應一個單例。
  • 工廠可實作為單例(MessageFactory.Instance),確保全域唯一池。
  • DI 容器註冊享元為 Singleton 生命週期,自動實現共享。

記憶體敏感場景的優化策略

  • 弱引用快取:Dictionary<string, WeakReference<Flyweight>>,記憶體壓力時允許 GC 回收享元,下次請求重建。
  • 定期清理:背景任務移除長時間未使用的享元。
  • 物件池:ObjectPool<Flyweight> 配合 IDisposable 歸還機制,適用於高頻建立/銷毀場景。

.NET 實際應用案例

  • String Interning:string.Intern() 共享相同字串字面值(編譯器自動、執行時手動)。
  • Enum 實例:每個列舉值在 AppDomain 單一實例。
  • WPF Freezable:凍結後可跨執行緒共享(筆刷、幾何圖形、轉換)。
  • Roslyn SyntaxNode:不可變語法樹節點大量共享。
  • Entity Framework 查詢計畫快取:相同 LINQ 運算式樹共享編譯後 SQL 命令。

優缺點總結

優點

  • 大幅減少記憶體佔用:N 個相似物件 → 1 個共享實例 + N 個外部狀態。
  • 外部狀態分離使享元可在不同上下文復用。
  • 無狀態享元天生執行緒安全,無需鎖定。

缺點

  • 增加系統複雜度:需分離內外部狀態、管理工廠池。
  • 執行時查找享元(字典查找)有微小效能開銷。
  • 若內部狀態變化頻繁/維度過多,享元池膨脹失去優勢。
  • 邏輯複雜度提升:除錯時需區分共享實例與外部狀態。

適用性判斷清單

  • 系統有大量相似物件導致記憶體壓力?
  • 物件大部分狀態可外部化?
  • 去除外部狀態後,剩餘內部狀態組合數量較少?
  • 客戶端能接受管理外部狀態的複雜度?

總結

享元模式以「內外部狀態分離 + 工廠池化共享」解決大量細粒度物件的記憶體問題。本範例以 SQL/XML 兩種儲存策略為享元,展示了 MessageFactory 如何透過字典池確保相同 key 回傳同一實例,客戶端操作時傳入 MessageModel 作為外部狀態。實務中,從字串驻留、枚舉實例、WPF Freezable 到編譯器語法樹、資料庫查詢計畫快取,享元模式是高效能系統的基石。掌握其核心在於「識別可共享的內部狀態、剝離外部狀態、設計高效鍵值工廠」,能讓你在面對「百萬級物件、記憶體受限、高併發」的系統挑戰時,從容應對並設計出極致優化的架構。


參考資源:DoFactory Flyweight Pattern

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved