為何需要單一性?#
通常類別與實例是一對多的關係,但有些類別應該只有一個實例——例如應用程式的根物件、工廠、管理器等。如果這些物件存在多個,可能導致邏輯錯誤、控制衝突或併發問題。
補充: 作者稱最簡單的做法為 JUST CREATE ONE 模式——初始化應用程式時只建立一個,不需要任何強制機制。在沒有立即且重要的需求時,這通常是最好的做法。但如果強制單一性的機制成本很低,其溝通意圖的好處可能超過機制的成本。
本章探討兩種強制單一性的模式:Singleton 和 Monostate,它們有截然不同的成本效益取捨。
Singleton 模式#
結構#
Singleton 透過私有建構函式和靜態變數來強制只能存在一個實例:
public class Singleton
{
private static Singleton theInstance = null;
private Singleton() {}
public static Singleton Instance
{
get
{
if (theInstance == null)
theInstance = new Singleton();
return theInstance;
}
}
}- 建構函式是
private,外部無法直接建立實例 - 只能透過
Singleton.Instance靜態屬性取得唯一實例 - 多次呼叫
Instance回傳的是同一個物件參考
優點#
- 跨平台(Cross-platform):透過適當的中介軟體(如 Remoting),可以擴展為跨多個 CLR 和多台電腦
- 適用於任何類別:任何類別都可以透過私有化建構函式加上靜態屬性變成 Singleton
- 可透過衍生建立:可以建立 Singleton 類別的子類別
- 延遲求值(Lazy Evaluation):如果 Singleton 從未被使用,就不會被建立
缺點#
- 銷毀未定義:沒有好的方式銷毀 Singleton。如果添加
decommission方法將theInstance設為 null,其他模組可能仍持有參考,後續呼叫Instance會建立新實例,導致兩個實例同時存在 - 不可繼承:從 Singleton 衍生的類別不是 Singleton,必須自行添加靜態變數和方法
- 效率:每次呼叫
Instance都要執行if判斷 - 不透明:使用者知道自己在使用 Singleton,因為必須呼叫
Instance方法
實際應用:UserDatabase#
以 Web 系統的使用者資料庫為例——用 Facade 模式建立 UserDatabase 類別封裝第三方 API,再用 Singleton 確保所有資料庫存取都經過單一實例,方便加入檢查、計數器和鎖定:
public class UserDatabaseSource : UserDatabase
{
private static UserDatabase theInstance =
new UserDatabaseSource();
public static UserDatabase Instance
{
get { return theInstance; }
}
private UserDatabaseSource() {}
// ReadUser, WriteUser 實作...
}Monostate 模式#
結構#
Monostate 用完全不同的機制達到單一性——所有變數都是 static,但方法都不是 static:
public class Monostate
{
private static int itsX;
public int X
{
get { return itsX; }
set { itsX = value; }
}
}- 無論建立多少個
Monostate實例,它們都表現得像同一個物件 - 在一個實例上設定
X的值,從另一個實例讀取X會得到相同的值 - 可以銷毀所有實例而不遺失資料
重點: Singleton 強制的是結構的單一性——阻止建立多於一個實例。Monostate 強制的是行為的單一性——所有實例表現相同,但不限制實例的數量。值得注意的是,Monostate 的測試案例對 Singleton 類別也有效,但反過來不行。
優點#
- 透明性(Transparency):使用者不需要知道物件是 Monostate,使用方式與普通物件完全相同
- 可衍生性(Derivability):Monostate 的衍生類別也是 Monostate,它們共享同一組靜態變數
- 多型性(Polymorphism):因為方法不是靜態的,可以在衍生類別中覆寫,提供同一組靜態變數上的不同行為
- 明確的建立與銷毀:靜態變數有明確的建立和銷毀時間
缺點#
- 不可轉換(No Conversion):非 Monostate 的類別無法透過衍生變成 Monostate
- 效率:因為是真實物件,可能經歷多次建立和銷毀,這些操作成本較高
- 持續佔用(Presence):即使 Monostate 從未使用,靜態變數仍佔用空間
- 平台限制(Platform Local):無法讓 Monostate 跨多個 CLR 實例或多個平台運作
實際應用:Turnstile FSM#
以地鐵旋轉門的有限狀態機(FSM)為例——Turnstile 設計為 Monostate,因為旋轉門永遠只有一個:

Figure 24.1: Subway turnstile finite state machine
Turnstile 基底類別將 Coin() 和 Pass() 事件委派到兩個衍生類別 Locked 和 Unlocked,它們代表 FSM 的狀態:
public class Turnstile
{
private static bool isLocked = true;
private static bool isAlarming = false;
private static int itsCoins = 0;
private static int itsRefunds = 0;
protected static readonly Turnstile LOCKED = new Locked();
protected static readonly Turnstile UNLOCKED = new Unlocked();
protected static Turnstile itsState = LOCKED;
public virtual void Coin() { itsState.Coin(); }
public virtual void Pass() { itsState.Pass(); }
// ...
}技巧: 測試方法假設
Turnstile是 Monostate,可以從不同實例發送事件並收集查詢結果。這很合理,因為旋轉門永遠只有一個。Monostate 讓衍生類別(Locked、Unlocked)自然地共享同一組狀態,很好地展示了 Monostate 的多型優勢。
Singleton vs. Monostate 比較#
| 面向 | Singleton | Monostate |
|---|---|---|
| 強制方式 | 結構(限制實例數量) | 行為(所有實例共享狀態) |
| 建立限制 | 只能一個實例 | 可建立多個實例 |
| 使用者認知 | 必須知道(呼叫 Instance) | 不需知道(透明使用) |
| 衍生 | 衍生類別不是 Singleton | 衍生類別也是 Monostate |
| 多型 | 不支援 | 支援(方法非靜態) |
| 銷毀 | 困難且不安全 | 可安全銷毀所有實例 |
| 跨平台 | 可以(透過中介軟體) | 不行(平台受限) |
| 延遲建立 | 支援 | 不支援(靜態變數立即建立) |