軟體設計模式系列之六——單例模式

2023-09-15 15:00:50

1 模式的定義

單例模式(Singleton Pattern)是一種常見的建立型設計模式,其主要目的是確保一個類只有一個範例,並提供一個全域性存取點來獲取該範例。這意味著無論何時何地,只要需要該類的範例,都會返回同一個範例,而不是建立多個相同的範例。單例模式通常用於管理全域性狀態、資源共用或限制某些資源的存取。

2 舉例說明

在日常生活中,隨處可見單例模式的例子,比如你家中有一臺電視,通常只需要一個遙控器來控制它。無論家裡誰想看電視,都會使用同一個遙控器,而且遙控器只能讓家裡人輪流使用,也就是不能有多個人同時使用遙控器控制電視。這個遙控器就是一個單例,因為它確保只有一個範例存在,並且提供了一個全域性的存取點,以便你可以隨時使用它。

3 結構

單例模式的結構包括以下要素:

  • 單例類(Singleton Class):單例模式的核心是單例類,它負責管理唯一的範例。通常,這個類會將其建構函式設為私有,以防止外部直接範例化多個物件。單例類會定義一個靜態方法或變數來獲取或建立唯一的範例。

  • 私有建構函式(Private Constructor):單例類別建構函式通常會被設定為私有,這樣外部無法直接範例化這個類。私有建構函式的目的是確保只有單例類內部可以建立類的範例。

  • 靜態成員變數(Static Member Variable):單例類會包含一個私有的靜態成員變數,用於儲存唯一的範例。這個成員變數通常被命名為 instance 或類似的名稱。

  • 靜態方法(Static Method):單例類會提供一個公共的靜態方法,通常命名為 getInstance() 或類似的名稱,用於獲取或建立唯一的範例。這個方法會檢查是否已經存在範例,如果存在則返回現有範例,否則建立一個新的範例並返回它。

單例模式的關鍵是將建構函式私有化,以確保只有一個範例,並提供一個全域性的方法來獲取這個範例,以實現全域性唯一性。這種結構確保了在應用程式中只有一個範例存在,無論何時何地都可以存取這個範例,從而實現了單例模式的設計目標。

4 實現步驟

實現單例模式的關鍵步驟通常包括以下幾個:

  1. 將建構函式私有化(Private Constructor):在單例模式中,首先需要將單例類別建構函式設為私有,以防止外部直接範例化多個物件。這是確保只有一個範例的重要步驟。

  2. 建立一個私有的靜態成員變數(Private Static Member Variable):單例類內部通常會包含一個私有的靜態成員變數,用於儲存唯一的範例。這個變數通常被命名為 instance 或類似的名稱。

  3. 提供一個公共的靜態方法(Public Static Method):單例類會提供一個公共的靜態方法,通常命名為 getInstance() 或類似的名稱,用於獲取或建立唯一的範例。這個方法會檢查是否已經存在範例,如果存在則返回現有範例,否則建立一個新的範例並返回它。

  4. 在獲取範例時進行範例化(Lazy Initialization):在 getInstance() 方法中,需要檢查 instance 是否為 None,如果為 None,則建立一個新的範例並將其賦值給 instance,否則直接返回 instance。這確保了範例在需要時才會被建立,避免了不必要的開銷。

  5. 處理多執行緒環境(Thread Safety):如果應用程式可能在多執行緒環境下使用單例類,需要考慮執行緒安全性。可以使用加鎖機制來確保在多執行緒環境下也只有一個範例被建立。

5 程式碼實現

在Java中,可以使用懶漢式和餓漢式兩種方式來實現單例模式。下面分別給出這兩種方式的範例程式碼:

懶漢式單例模式
在懶漢式中,範例是在首次被請求時才建立。

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {
        // 私有建構函式,防止外部範例化
    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

在懶漢式中,getInstance 方法首先檢查範例是否已經建立。如果沒有建立範例,則建立一個新的範例並返回。這種實現延遲了範例的建立,只有在需要時才會建立。

餓漢式單例模式
在餓漢式中,範例在類載入時就被建立,無論是否需要。

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {
        // 私有建構函式,防止外部範例化
    }

    public static EagerSingleton getInstance() {
        return instance;
    }
}

在餓漢式中,範例在類載入時就被建立,因此無論何時需要範例,都可以立即返回。這種實現簡單且執行緒安全,但可能會造成資源浪費,因為範例會在應用程式啟動時就被建立。

需要注意的是,懶漢式在多執行緒環境下需要額外的同步措施來確保執行緒安全,而餓漢式天生是執行緒安全的。選擇使用哪種方式取決於具體的需求和效能考慮。

6 典型應用場景

單例模式在各種應用場景中都有廣泛的應用,主要用於確保一個類只有一個範例,並提供全域性存取點。以下是一些常見的單例模式應用場景:

資料庫連線池:在大多數應用程式中,與資料庫的互動是常見的操作。為了提高效能和資源利用率,應用程式通常會使用資料庫連線池來管理資料庫連線。單例模式可以用於確保只有一個資料庫連線池的範例存在,以避免多次建立和銷燬資料庫連線。

執行緒池:執行緒池用於管理和控制執行緒的執行。通過使用單例模式,可以確保只有一個執行緒池範例,從而更有效地管理並行執行的任務。

設定管理:在應用程式中,通常需要讀取和管理設定資訊,例如資料庫連線引數、應用程式設定等。單例模式可用於儲存和管理這些設定資料,以確保在整個應用程式中使用相同的設定。

紀錄檔記錄器:在應用程式中記錄紀錄檔是一項重要的任務,通常會使用紀錄檔記錄器來處理紀錄檔資訊。通過單例模式,可以確保只有一個紀錄檔記錄器範例,以避免多次初始化和設定紀錄檔記錄器。

視窗管理器:在圖形化使用者介面應用程式中,視窗管理器用於管理應用程式視窗的建立、銷燬和切換。單例模式可用於確保只有一個視窗管理器範例,以維護視窗狀態和順序。

單例模式在需要確保全域性唯一性、資源共用、全域性存取和狀態管理的各種應用場景中非常有用。它可以幫助簡化程式碼、提高效能,並確保應用程式的一致性。然而,需要謹慎使用,以避免引入全域性狀態和多執行緒問題。

7 優缺點

優點:
全域性存取點:通過單例模式,可以在應用程式的任何地方輕鬆存取相同的範例。
資源共用:單例模式可用於管理共用的資源,例如資料庫連線、執行緒池等,以提高效能和資源利用率。
避免重複建立:單例模式確保只有一個範例,避免了重複建立物件的開銷。
缺點:
可能引入全域性狀態:過度使用單例模式可能導致全域性狀態,使得程式碼難以維護和測試。
不適用於多執行緒環境:如果不正確地實現單例模式,可能會導致多執行緒競態條件,需要額外的同步機制來解決。

8 類似模式

在軟體開發中,單例模式和原型模式通常在建立和管理"bean"(也稱為物件或元件)時發揮重要作用,但它們在此上下文中有不同的用途和應用場景。

單例模式在bean的建立中的應用:

Spring框架中的單例bean:在Spring框架中,預設情況下,Spring容器會將Bean設定為單例(Singleton)。這意味著每個bean在應用程式中只有一個範例,並且Spring容器負責管理這些單例bean的生命週期。這種單例模式的應用確保了全域性唯一性,並且可以節省資源和提高效能。
原型模式在bean的建立中的應用:

原型範圍的Spring bean:在Spring框架中,你可以將bean設定為原型(Prototype)範圍,這意味著每次從Spring容器請求該bean時,都會建立一個新的範例。原型模式的應用適用於那些需要頻繁建立新範例的場景,例如HTTP請求的處理,每個請求需要一個新的bean範例以避免狀態共用。
關係和應用場景:

單例模式通常用於那些需要確保全域性唯一性的bean,例如服務層的單例元件、資料庫連線池、設定管理器等。它適用於那些需要共用狀態或資源的情況。

原型模式通常用於那些需要頻繁建立新範例的bean,例如Web應用程式中的請求處理器、執行緒池中的任務、HTTP對談管理器等。它適用於那些需要隔離狀態或資源的情況。

在Spring框架中,你可以根據bean的具體需求將它們設定為單例或原型範圍,以滿足應用程式的不同要求。這兩種模式有各自的優勢和適用場景,可以根據業務邏輯和效能要求來選擇合適的範圍。

9 小結

單例模式是一種有用的設計模式,用於確保一個類只有一個範例,並提供全域性存取點。它在多種應用場景中都有用武之地,但需要小心使用,以避免引入全域性狀態和多執行緒問題。通過將建構函式私有化、使用靜態變數儲存範例以及提供一個靜態方法來獲取範例,可以實現單例模式。在設計應用程式時,要考慮是否需要使用單例模式來滿足特定的需求。