Sam Saariste
Singleton 的誘惑#
Singleton pattern 看似能解決許多問題:你知道只需要一個實例;它保證在使用前就已初始化;透過全域存取點讓設計變得簡單。這個經典設計模式有什麼不好的呢?
事實上,問題很大。經驗表明,大多數 singleton 造成的壞處多於好處,它們阻礙了可測試性並損害了可維護性。以下是需要抵抗 singleton 誘惑的理由:
Singleton 的問題#
- 單一實例的需求往往是想像出來的:在許多情況下,這只是揣測未來不需要更多實例。將這種推測性的屬性設計到應用程式架構中,未來需求改變時必定造成痛苦。好的設計擁抱變化,singleton 不會
- Singleton 在概念上獨立的程式碼單元之間造成隱式依賴:這些依賴既隱藏又引入不必要的耦合。當你嘗試撰寫需要**鬆耦合(loose coupling)**的單元測試時,這個問題會變得特別明顯——singleton 阻止了用 mock 實作替換真實實作
- Singleton 帶有隱式的持久狀態,妨礙單元測試:單元測試要求測試之間互相獨立、可以任意順序執行、可以在每次測試前設定已知狀態。一旦引入具有可變狀態的 singleton,這些都很難實現。全域可存取的持久狀態也讓程式碼更難推理,尤其在多執行緒環境中
- 多執行緒為 singleton 帶來更多陷阱:由於直接的存取鎖定效率不佳,所謂的 Double-Checked Locking Pattern(DCLP) 越來越流行。但在許多語言中 DCLP 並非 thread-safe,即使是正確的實作也有微妙的出錯空間
Singleton 的清除問題#
- 沒有明確銷毀 singleton 的支援:例如在 plug-in 架構中,這可能是個嚴重問題
- 程式結束時 singleton 的隱式清除沒有固定順序:對於有相互依賴的 singleton 應用程式,關閉時一個 singleton 可能存取到另一個已被銷毀的 singleton
正確做法#
將 singleton 的使用限制在真正只能被實例化一次的類別。不要把 singleton 的全域存取點當作任意程式碼的入口。
正確的做法是:只從少數定義明確的地方直接存取 singleton,並透過其介面傳遞給其他程式碼。其他程式碼不知道也不關心它是 singleton 還是其他類別——這打破了阻礙單元測試的依賴關係,並提升了可維護性。