1. 程式人生 > >確保物件的唯一性——單例模式 (一)

確保物件的唯一性——單例模式 (一)

3.1 單例模式的動機

      對於一個軟體系統的某些類而言,我們無須建立多個例項。舉個大家都熟知的例子——Windows工作管理員,如圖3-1所示,我們可以做一個這樣的嘗試,在Windows的“工作列”的右鍵彈出選單上多次點選“啟動工作管理員”,看能否開啟多個工作管理員視窗?如果你的桌面出現多個工作管理員,我請你吃飯,微笑(注:電腦中毒或私自修改Windows核心者除外)。通常情況下,無論我們啟動任務管理多少次,Windows系統始終只能彈出一個工作管理員視窗,也就是說在一個Windows系統中,工作管理員存在唯一性。為什麼要這樣設計呢?我們可以從以下兩個方面來分析:其一,如果能彈出多個視窗,且這些視窗的內容完全一致,全部是重複物件,這勢必會浪費系統資源,工作管理員需要獲取系統執行時的諸多資訊,這些資訊的獲取需要消耗一定的系統資源,包括CPU

資源及記憶體資源等,浪費是可恥的,而且根本沒有必要顯示多個內容完全相同的視窗;其二,如果彈出的多個視窗內容不一致,問題就更加嚴重了,這意味著在某一瞬間系統資源使用情況和程序、服務等資訊存在多個狀態,例如工作管理員視窗A顯示“CPU使用率”為10%,視窗B顯示“CPU使用率”為15%,到底哪個才是真實的呢?這純屬“調戲”使用者,偷笑,給使用者帶來誤解,更不可取。由此可見,確保Windows工作管理員在系統中有且僅有一個非常重要。

         3-1 Windows工作管理員

      回到實際開發中,我們也經常遇到類似的情況,為了節約系統資源,有時需要確保系統中某個類只有唯一一個例項,當這個唯一例項建立成功之後,我們無法再建立一個同類型的其他物件,所有的操作都只能基於這個唯一例項。為了確保物件的唯一性,我們可以通過單例模式來實現,這就是單例模式的動機所在。

3.2 單例模式概述

      下面我們來模擬實現Windows工作管理員,假設工作管理員的類名為TaskManager,在TaskManager類中包含了大量的成員方法,例如建構函式TaskManager(),顯示程序的方法displayProcesses(),顯示服務的方法displayServices()等,該類的示意程式碼如下:

class TaskManager
{
     public TaskManager() {……} //初始化視窗
     public void displayProcesses()  {……} //顯示程序
     public void  displayServices() {……} //顯示服務
     ……
}

       為了實現Windows工作管理員的唯一性,我們通過如下三步來對該類進行重構:

      (1)  由於每次使用new關鍵字來例項化TaskManager類時都將產生一個新物件,為了確保TaskManager例項的唯一性,我們需要禁止類的外部直接使用new來建立物件,因此需要將TaskManager的建構函式的可見性改為private,如下程式碼所示:

private TaskManager() {……}

       (2)  將建構函式改為private修飾後該如何建立物件呢?不要著急,雖然類的外部無法再使用new來建立物件,但是在TaskManager的內部還是可以建立的,可見性只對類外有效。因此,我們可以在TaskManager中建立並儲存這個唯一例項。為了讓外界可以訪問這個唯一例項,需要在TaskManager中定義一個靜態的TaskManager型別的私有成員變數,如下程式碼所示:

private static TaskManager tm = null;

(3)  為了保證成員變數的封裝性,我們將TaskManager型別的tm物件的可見性設定為private,但外界該如何使用該成員變數並何時例項化該成員變數呢?答案是增加一個公有的靜態方法,如下程式碼所示:

public static TaskManager getInstance()
{
    if (tm == null)
    {
        tm = new TaskManager();
    }
    return tm;
}

       在getInstance()方法中首先判斷tm物件是否存在,如果不存在(即tm == null),則使用new關鍵字建立一個新的TaskManager型別的tm物件,再返回新建立的tm物件;否則直接返回已有的tm物件。

      需要注意的是getInstance()方法的修飾符,首先它應該是一個public方法,以便供外界其他物件使用,其次它使用了static關鍵字,即它是一個靜態方法,在類外可以直接通過類名來訪問,而無須建立TaskManager物件,事實上在類外也無法建立TaskManager物件,因為建構函式是私有的。

思考

為什麼要將成員變數tm定義為靜態變數?

通過以上三個步驟,我們完成了一個最簡單的單例類的設計,其完整程式碼如下:

class TaskManager
{
     private static TaskManager tm = null;
     private TaskManager() {……} //初始化視窗
     public void  displayProcesses() {……} //顯示程序
     public void  displayServices() {……} //顯示服務
     public static TaskManager getInstance()
     {
        if (tm == null)
        {
            tm = new TaskManager();
        }
        return tm;
    }
   ……
}

       在類外我們無法直接建立新的TaskManager物件,但可以通過程式碼TaskManager.getInstance()來訪問例項物件,第一次呼叫getInstance()方法時將建立唯一例項,再次呼叫時將返回第一次建立的例項,從而確保例項物件的唯一性。

      上述程式碼也是單例模式的一種最典型實現方式,有了以上基礎,理解單例模式的定義和結構就非常容易了。單例模式定義如下:

單例模式(Singleton Pattern):確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項,這個類稱為單例類,它提供全域性訪問的方法。單例模式是一種物件建立型模式。

      單例模式有三個要點:一是某個類只能有一個例項;二是它必須自行建立這個例項;三是它必須自行向整個系統提供這個例項。

       單例模式是結構最簡單的設計模式一,在它的核心結構中只包含一個被稱為單例類的特殊類。單例模式結構如圖3-2所示:

      單例模式結構圖中只包含一個單例角色:

      ● Singleton(單例):在單例類的內部實現只生成一個例項,同時它提供一個靜態的getInstance()工廠方法,讓客戶可以訪問它的唯一例項;為了防止在外部對其例項化,將其建構函式設計為私有;在單例類內部定義了一個Singleton型別的靜態物件,作為外部共享的唯一例項。