Java單例模式:缺點和優點

banq發表於2024-04-07

Singleton 是Gof 四人幫於 1994 年引入的一種創造性設計模式,由於其簡單的實現而經常被誤用而受到批評。因此,它已演變成現代軟體開發實踐中的反模式。

讓我們深入瞭解 Java 模式、單例的優缺點。

什麼是單例設計模式
單例設計模式是一種建立模式,可確保類只有一個例項並提供對該例項的全域性訪問點。單例模式通常用於需要類的單個例項來控制操作、資源或配置的場景。
1.1 主要特點

  • 單例項:單例模式確保在應用程式的整個生命週期中僅建立該類的一個例項。這是透過提供一種控制例項化過程的機制來實現的,通常使用私有建構函式來防止外部例項化,並使用靜態方法來訪問唯一例項。透過強制執行單個例項約束,該模式提高了記憶體效率和資源節約,因為類的多個例項是不必要的,並且可能導致不必要的開銷。此外,單個例項可確保整個應用程式狀態的一致性和連貫性,因為所有客戶端都與同一物件例項互動,從而防止資料重複和同步問題。
  • 全域性訪問:Singleton 提供了一個全域性可訪問的例項,可以從應用程式的任何部分進行訪問。這種全域性可訪問性簡化了系統不同元件之間的通訊和協作,因為 Singleton 例項充當互動和協調的集中點。透過消除顯式傳遞引用或共享例項的需要,該模式減少了元件之間的耦合並促進了模組化、解耦設計。此外,全域性訪問有助於實現橫切關注點,例如日誌記錄、快取或配置管理,因為這些功能可以封裝在 Singleton 例項中,並可以從應用程式中的任何位置進行統一訪問。
  • 延遲初始化:許多單例實現都支援延遲初始化,其中例項僅在客戶端程式碼首次請求時才建立。這種延遲例項化策略將物件建立推遲到需要時為止,從而縮短了應用程式啟動時間並減少了記憶體消耗。延遲初始化可以使用各種技術來實現,例如延遲載入、雙重檢查鎖定或靜態內部類初始化。透過將物件建立延遲到執行時,延遲初始化可以實現更好的資源利用率和可擴充套件性,特別是在記憶體受限或資源密集型環境中。此外,延遲初始化可以透過隨時間分配資源分配來提高應用程式效能,從而防止啟動或初始化階段出現瓶頸。
  • 熱切初始化:雖然延遲初始化是 Singleton 實現中的常見方法,但某些場景可能需要熱切初始化,其中 Singleton 例項是在應用程式啟動或類載入時建立的。熱切初始化可確保 Singleton 例項在需要時立即可用,從而避免與延遲初始化相關的潛在延遲或競爭條件。這對於建立 Singleton 例項的開銷最小或急切初始化對於維護應用程式狀態或確保執行緒安全至關重要的應用程式來說是有益的。然而,急切的初始化可能會增加應用程式的啟動時間和記憶體消耗,特別是對於具有繁重初始化邏輯或資源密集型構造的 Singleton 例項。

實施
要實現單例模式,通常需要類:

  • 提供訪問例項的靜態方法(通常命名為getInstance())。
  • 有一個私有建構函式來防止從外部例項化該類。
  • 維護對唯一例項的靜態引用。

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {
        <font>// 防止例項化的私有建構函式<i>
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

所提供的 Java 程式碼實現了 Singleton 設計模式:

  • Singleton 類宣告瞭一個名為 instance of type Singleton 的私有靜態變數。該變數是該類的唯一例項。
  • Singleton 類的建構函式被標記為私有,以防止從外部直接例項化該類。
  • getInstance() 方法被宣告為公共和靜態方法,提供了一個訪問 Singleton 例項的全域性點。在該方法中,它會檢查例項變數是否為空。如果為空,則使用私有建構函式建立一個新的單例類例項,並將其分配給例項變數。
  • 對 getInstance() 的後續呼叫將返回現有例項。這樣可以確保只建立一個單例類例項,並提供全域性訪問的方式。

益處
資源共享:單例有利於在整個應用程式中高效共享資源。透過提供單一的、全域性可訪問的例項,該模式可確保資料庫連線、檔案控制代碼或昂貴物件等資源在整個應用程式中共享和重用,而不是不必要地建立多個例項。這可以顯著提高效能和資源利用率,尤其是在記憶體或其他資源有限的資源受限環境中。此外,Singleton 對資源的集中管理可以簡化資源清理和生命週期管理,降低資源洩漏的風險,提高系統的整體穩定性。

執行緒安全:單例實現可提供內建執行緒安全機制,確保在多執行緒環境中安全訪問例項。透過同步方法或鎖控制對單例例項的訪問,該模式可防止多個執行緒試圖同時訪問或修改共享資源時可能出現的競賽條件和資料損壞。這簡化了併發管理,減少了開發人員實施自定義同步邏輯的需要,提高了程式碼的可靠性和可維護性。此外,執行緒安全的 Singletons 允許多個執行緒同時安全地訪問 Singleton 例項,最大限度地提高了資源利用率和效能,從而促進了並行執行和可擴充套件性。

配置管理:單件可有效地用於集中管理應用程式的配置設定。透過在 Singleton 例項中封裝配置引數,該模式為在整個應用程式中檢索和更新配置值提供了單一訪問點。這促進了一致性,並確保配置更改在整個系統中統一應用,降低了配置漂移和不一致的風險。此外,Singleton 還能支援執行時的動態配置更新,使更改立即生效,而無需重新啟動應用程式或停機,這對於保持系統的可用性和響應速度至關重要。

缺點
全域性狀態:Singleton 在應用程式中引入了全域性狀態,這會導致幾個問題。首先,它會使程式碼更難理解和推理,因為程式碼庫的任何部分都有可能修改 Singleton 例項。這可能會導致意想不到的互動和副作用,從而難以預測系統的行為。此外,全域性狀態會增加系統不同元件之間的耦合,從而妨礙程式碼的可維護性和可擴充套件性。對單例的更改會對整個應用程式產生廣泛的影響,從而使隔離和管理依賴關係變得十分困難。

併發性:在多執行緒環境中,Singleton 實現必須確保執行緒安全,以防止資料損壞和競賽條件。如果沒有適當的同步機制,多個執行緒同時訪問或修改 Singleton 例項會導致不一致或未定義的行為。雖然懶惰初始化技術(如雙重檢查鎖定或同步塊)可以緩解一些併發問題,但它們會帶來效能開銷和複雜性。此外,過度同步會導致爭用,降低並行性,影響應用程式的可擴充套件性和效能。

測試:單例依賴會使單元測試複雜化並妨礙可測試性。由於 Singletons 代表全域性狀態或服務,它們會在依賴於它們的類中引入隱藏的依賴關係,這使得隔離和孤立測試單個元件變得非常困難。為了便於進行單元測試,可能有必要對 Singleton 例項進行模擬或存根處理,但這會導致繁瑣的測試設定,以及與 Singleton 的實現細節緊密耦合的脆性測試。此外,無法用替代實現來替代 Singleton 依賴關係,也會限制測試策略的範圍和有效性,從而難以實現全面的測試覆蓋。

作為依賴項的單例:當大量依賴單件時,依賴注入就變得更具挑戰性。在整個程式碼庫中,通常都會靜態訪問 Singleton 例項,因此很難用替代實現或模擬物件來代替它們進行測試。這種緊密耦合會妨礙程式碼的靈活性和可維護性,因為更改 Singleton 介面或行為可能需要修改應用程式的多個部分。此外,Singleton 模式會阻礙適當的依賴反轉和模組化設計實踐,導致程式碼更難擴充套件、重構和維護。

單例設計模式的替代方案
單例設計模式雖然被廣泛使用,但也有一定的缺點,並且可能並不總是管理全域性狀態或控制物件例項化的最佳選擇。幸運的是,有幾種替代方案可以解決不同的用例,並提供更靈活和可擴充套件的解決方案。

  • 依賴注入:依賴注入(DI)是一種將依賴項注入到類中而不是由類本身例項化或管理的模式。 Spring、Guice 或 Dagger 等 DI 框架有助於在執行時注入依賴項,從而實現鬆散耦合並提高可測試性。透過將物件的建立和管理與客戶端程式碼解耦,DI 促進了模組化、可維護的設計,並簡化了跨應用程式的依賴關係處理。
  • 工廠方法:工廠方法模式提供了一個用於建立物件的介面,而無需指定其具體類。透過將物件建立的責任委託給工廠類,該模式提高了封裝性和靈活性,允許基於執行時條件或配置進行動態例項化。與Singleton不同,Factory Method可以建立物件的多個例項,或者根據上下文返回不同的實現,適合物件建立邏輯複雜或容易變化的場景。
  • 原型:原型模式涉及透過複製現有原型例項來建立新物件。每個客戶端都可以建立和修改其原型副本,而不是依賴於單個全域性例項,從而實現更好的定製和狀態隔離。雖然 Prototype 與 Singleton 相似,都管理物件建立,但不同之處在於,Prototype 促進物件克隆和可變性,使其適合物件初始化成本昂貴或需要物件的多個變體的場景。
  • 服務定位器:服務定位器模式集中管理和查詢應用程式內的服務或元件。透過提供全域性登錄檔或定位器物件,該模式使客戶端能夠動態檢索依賴項,而無需耦合到其具體實現。雖然 Service Locator 與 Singleton 的相似之處在於它提供了對例項的全域性訪問,但不同之處在於它將例項化和查詢委託給單獨的服務登錄檔,從而促進了服務解析的關注點分離和靈活性。
  • Multiton:Multiton 模式是 Singleton 的擴充套件,其中一個類的多個命名例項可以在應用程式中共存。每個例項都由唯一的金鑰或名稱標識,允許客戶端根據自己的要求請求特定例項。與全域性管理單個例項的 Singleton 不同,Multiton 提供命名例項池,從而對物件例項化和生命週期提供更精細的控制。這在不同的上下文或配置需要不同的類例項的情況下非常有用。

相關文章