軟體設計

Swift Monostate Pattern:能成為 Singleton 的替代方案嗎?

獻給曾懷疑 Singleton 真的適合一直這樣使用的人

閱讀 4 分鐘
Swift Monostate Pattern:能成為 Singleton 的替代方案嗎? 封面圖

獻給曾懷疑 Singleton 真的適合一直這樣使用的人

做 iOS 開發時,應該沒有人完全沒用過 Singleton。我也一樣。SomeManager.shared 這種程式碼在每個專案裡至少都會有好幾段。

但從某個時候開始,這個.shared逐漸變得令人不舒服。測試時狀態會互相干擾,團隊成員也會從各處任意存取它。

後來我認識了 Monostate Pattern。今天想分享我親自使用 Swift Monostate 的經驗,以及它是否真的能成為 Singleton 的替代方案。

先說結論,Monostate 是「共享同一份狀態,但允許建立多個執行個體」的模式。目的和 Singleton 相同,但做法完全相反。不過它不是萬靈丹,還是要看情境。下面會逐一說明。

Monostate Pattern 到底是什麼?

Singleton 的核心是「只建立一個執行個體」。限制初始化器,讓程式只使用shared

Monostate 則相反。執行個體可以建立任意多個,但其中的狀態(資料)全部透過 static 共享。

也就是說,不論建立物件 A 或 B,兩者看到的資料都在同一處。外觀看起來是普通物件,內部卻連接到同一份狀態。

用 Swift 簡單表示,大致是這個樣子。

struct Settings {
    private static var _volume = 50   // 共享 static狀態
    var volume: Int {                 // 像執行個體一樣使用
        get { Settings._volume }
        set { Settings._volume = newValue }
    }
}
// 不同執行個體,但狀態只有一份
var a = Settings(); a.volume = 80
print(Settings().volume)  // 80

即使重新建立Settings(),volume 仍會是 80。執行個體有多個,但狀態只有一份,這就是 Monostate 的全部。

執行個體有三個,但它們看到的是同一份狀態
執行個體有三個,但它們看到的是同一份狀態

它和 Singleton 有什麼不同?

最大的差異在於使用端的程式碼。

使用 Singleton 時,呼叫端必須一直意識到.shared,心裡要想著「啊,這是 Singleton」。Monostate 則只要像普通物件一樣建立Settings()並使用,使用端不需要知道內部實作。

整理成表格如下。

分類 Singleton Monostate
執行個體數量 只有 1 個 可建立多個
共享的內容 執行個體本身 狀態(static 資料)
使用端 明確指定.shared 像普通物件一樣
繼承 棘手 相對自由

尤其是繼承很有意思。Singleton 很難繼承,但 Monostate 是一般型別,因此擴充起來更加靈活。


那麼,它能成為 Singleton 的替代方案嗎?

老實說,有時候可以,有時候不行。

先談優點。使用端的程式碼會更簡潔,.shared這個全域存取點也不會散落在程式碼各處。介面和普通物件完全一樣,因此日後變更結構時負擔也比較小。

Monostate 是「隱藏全域狀態」的模式。看似方便,但隱藏的全域狀態本身就是一把雙面刃。

問題就在這裡。它外觀看起來像普通物件,團隊成員可能以為「這只是區域物件」而建立它,結果才發現狀態其實是全域共享的,反而更容易混淆。

而且因為是 static 共享,在多執行緒環境中仍然要同樣注意並行處理問題。這一點和 Singleton 沒有差別。

依我的經驗,在現代 Swift 中,我建議優先考慮相依性注入,而不是 Monostate。這樣更容易測試,也能讓相依關係更清楚。

如果是現在的 Swift 專案,我會先拿出 DI
如果是現在的 Swift 專案,我會先拿出 DI

那麼,什麼時候適合使用呢?

總結來說,適合以下情況。

  1. 現有 Singleton 程式碼太多,想逐步移除時
  2. 想讓使用端介面維持像普通物件一樣簡潔時
  3. 需要處理必須支援繼承或擴充的全域狀態時

反過來說,如果是從零開始撰寫新專案,我會建議先考慮相依性注入或明確的狀態管理,而不是 Monostate。

我會把模式放進工具箱,在適合的情境拿出來使用
我會把模式放進工具箱,在適合的情境拿出來使用

歸根究柢,Monostate 不是「Singleton 的完美上位替代」,而是「個性不同的另一個選項」。把它放進工具箱,在適合的情況下拿出來使用即可。

希望今天的內容能為疲於應付.shared濫用的人提供一點小提示。與其盲目改用其他方案,不如花點時間想想什麼最適合自己的專案。

延伸閱讀