設計模式-創建模式之Singleton

更多請移步我的博客

意圖

Singleton是創建模式的一種,讓你可以確保一個類只有一個實例,併爲此實例提供一個全局訪問點。

問題

Singleton同時解決了兩個問題(違反了單一職責原則):

  1. 確保一個類只有一個實例。最常見原因是控制一些共享資源,比如,數據庫。

    假設你已經創建了一個新對象,不久,又嘗試創建一個新的。在這種秦光下,你想要老的那個對象而不是新創建一個實例。

    它不能通過正常的構造方法完成,因爲在設計上每個構造方法總是返回一個新對象。

  2. 爲實例提供一個全局的訪問點。聽起來像一個全局變量,不是嗎?但是你無法做一個只讀的全局變量。任何可以訪問這個變量的人都可以替換他的值。

    還有另外一個問題:你不希望解決以前的問題的代碼分散在你的程序中。它們最好放在一個類中,特別當你的代碼已經依賴那個類時。

注意,Sigleton同時解決了上面兩個問題。但是現在模式很流行,即使他只解決了其中一個問題人們也會把它稱爲Sigleton。

解決

單例所有實現的都有以下兩個步驟:

  • 創建私有的構造方法。

  • 創建靜態的創建方法扮演構造方法的角色。這個方法使用私有的構造方法創建一個對象並把它保存在靜態變量或者字段中。對這個方法的所有調用都將返回緩存的對象。

Singleton保持把單實例的生產代碼放在一個地方–Singlton類的創建方法中。任何可以訪問Singleton類的客戶端也都可以訪問他的創建方法。因此,他提供給我們Singleton實例的一個全局訪問點。

真實世界的類比

政府

政府是Singleton模式的一個很好的例子。一個國家只能有一個官方政府。不管組件政府的個人身份如何,“X的政府”這個稱號是全球的一個訪問點,他可以識別這個組織的負責人。

結構

structure

  1. Singleton聲明靜態的方法getInstance(),這個方法返回相同的Singleton類實例。

Singleton的構造方法對客戶端代碼應當不可見。getInstance()應該是唯一的可以創建並獲得Singleton對象的途徑。

僞代碼

在這個例子中,數據庫連接類扮演Singleton角色。這個類沒有公開的構造方法,所以只有調用getInstance方法可以獲取這個對象。這個方法混存第一次創建的對象,在隨後所有的調用中都返回它。

單例模式保證他的類只有一個實例被創建。並且,他提供了實例全局訪問點:這個靜態方法getInstance。

class Database is
    private field instance: Database

    static method getInstance() is
        if (this.instance == null) then
            acquireThreadLock() and then
                // Ensure that instance has not yet been initialized by other
                // thread while this one has been waiting for the lock release.
                if (this.instance == null) then
                    this.instance = new Database()
        return this.instance

    private constructor Database() is
        // Some initialization code, such as the actual connection to a
        // database server.
        // ...

    public method query(sql) is
        // All database queries of an app will go through this methods.
        // Therefore, you can place a throttling or caching logic here.
        // ...

class Application is
    method main() is
        Database foo = Database.getInstance()
        foo.query("SELECT ...")
        // ...
        Database bar = Database.getInstance()
        bar.query("SELECT ...")
        // The variable `bar` will contain the same object as the variable `foo`.

適用性

  • 當程序需要提供給所有客戶端一個類的一個可以實例。比如,一個單獨的數據庫對象,在程序的不同模塊貢獻。

    除了特別的的創建方法,Singleton對客戶端隱藏了所有創建類的新對象的方法。這個方法創建一個新對象或者返回之前已經創建過的已經存在的對象。

  • 當你需要嚴格控制全局變量。

    不想全局變量,Singleton保證只有一個類實例。除了Singleton本身,沒有任何類可以替換緩存的實例。

    Sigleton讓你可以輕鬆改變這個限制。比如,允許任何數量的實例,你只需要在一個地方修改代碼–getInstance()方法體內。

如何實現

  1. 在類中添加一個靜態字段用來持有單實例。

  2. 聲明靜態的公開創建方法,它將用來檢索單實例。

  3. 在創建方法中實現“懶初始化”。它應該在第一次調用時創建一個新實例,並把它放到靜態變量中。在隨後的調用中這個方法都返回這個實例。

  4. 把類的構造方法聲明爲私有。

  5. 把客戶端代碼中所有直接對構造方法的調用替換爲對創建方法的調用。

優點

  • 保證類只有一個實例。

  • 提供實例的全局訪問點。

  • 允許懶實例。

缺點

  • 違背單一職責原則。

  • 面具壞設計(Masks bad design?)。

  • 在多線程的環境下需要特別處理。

  • 在單元測試中要無盡mock。

和其他模式的關係

  • Facade可以改造成Singleton,因爲大多情況下,一個門面對象就足夠了。

  • 在一些情況下Flyweight和Sigleton很像,Flyweight把什麼事情都減少到一個享元對象。但是記住,它們之間有兩個基本的不同:

    1.Singleton對象時易變的。Flyweight對象時不變的。

    1. 單例類只有一個類實例,而享元類有多個不同狀態的實例。
  • Abstract Factory,Builder和Prototype都可以實現爲Singleton。

小結

Singleton在優缺點方面幾乎和全局變量一樣。儘管它們很好用,但卻破壞了你代碼的模塊化。

在其他的上下文中,你可以使用一個依賴於Singleton的類。你將不得不攜帶Singleton類。大多數時候,在創建單元測試時會出現這個限制。

儘管許多開發着認爲Singleton是反模式,但是在Java的核心類中也有許多例子:

  • java.lang.Runtime#getRuntime()

  • java.awt.Desktop#getDesktop()

  • java.lang.System#getSecurityManager()

Singleton可以通過一個返回相同緩存對象的靜態創建方法來識別。

參考

翻譯整理自:https://refactoring.guru/design-patterns/singleton

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章