「單例模式」與「靜態類」同樣提供「全域唯一存取點」,卻在本質、生命週期、擴充性上有本質差異。本文透過代碼對比、記憶體模型分析與實戰案例,幫助你在正確場景做正確選擇。
| 維度 | 單例模式 | 靜態類 |
|---|---|---|
| 實例化時機 | 延遲載入,首次呼叫 GetInstance() 時建立 | 型別載入時由 CLR 自動初始化(靜態建構子) |
| 記憶體管理 | 存放於 Managed Heap,受 GC 管理,可被回收(若無根引用) | 存放於 High-Frequency Heap (Loader Heap),AppDomain 卸載前常駐 |
| 介面實作 | 可實作介面,支援多型與依賴注入 | 不可實作介面,無法多型 |
| 繼承 | 可繼承(若非 sealed),支援擴充 | 隱含 sealed,不可繼承 |
| 建構子參數 | 可透過工廠方法傳參初始化 | 僅靜態建構子,無法傳參 |
| 序列化 | 可序列化/反序列化 | 不可序列化 |
| 鎖定同步 | 雙重檢查鎖定、Lazy<T> 等多種線程安全模式 | CLR 保證型別初始化線程安全 |
public sealed class SiteUrls {
private static readonly object _lock = new object();
private static volatile SiteUrls _instance = null;
private ArrayList _urls;
private NameValueCollection _paths;
private SiteUrls() {
_urls = new ArrayList();
_paths = new NameValueCollection();
// 從資料庫載入 URL 重寫規則
var bll = new BLL.url_rewrite();
var list = bll.GetList("");
foreach (var model in list) {
_paths.Add(model.name, model.path);
model.page = model.page.Replace("^", "&");
model.querystring = model.querystring.Replace("^", "&");
_urls.Add(model);
}
}
public static SiteUrls GetSiteUrls() {
// 先嘗試從快取取得
var cached = CacheHelper.Get<SiteUrls>(DTKeys.CACHE_SITE_HTTP_MODULE);
if (cached != null) return cached;
lock (_lock) {
if (_instance == null) {
_instance = new SiteUrls();
// 寫入快取,並設定檔案相依性自動失效
CacheHelper.Insert(DTKeys.CACHE_SITE_HTTP_MODULE, _instance,
Utils.GetXmlMapPath(DTKeys.FILE_URL_XML_CONFING));
}
return _instance;
}
}
public ArrayList Urls { get => _urls; set => _urls = value; }
public NameValueCollection Paths { get => _paths; set => _paths = value; }
}
CacheHelper.Insert(..., fileDependency),當設定檔變更時快取自動失效,下次呼叫 GetSiteUrls() 會重新 new 實例並重新載入資料。靜態類無法實現「重新初始化」。_urls、_paths 為實例欄位,可在執行期動態增減。靜態類唯有靜態欄位,同樣可變,但缺乏「重置」機制。IUrlProvider 抽象,單元測試時注入 Mock 物件。靜態類難以 Mock。Math, StringHelper, DateTimeExtensions)單例模式以「受控的實例化」換取靈活性:延遲載入、參數化建構、快取失效重載、介面抽象、可測試。靜態類以「零開銷的型別層級服務」換取極致效能與簡潔。現代 .NET 開發傾向:預設選單例(配合 Lazy<T> 或 DI 容器管理生命週期),唯有純工具集才用靜態類。