設計模式(一)單例模式(Singleton Pattern)

1、引言

最近在設計模式的一些內容,主要的參考書籍是《Head First 設計模式》,同時在學習過程當中也查看了不少博客園中關於設計模式的一些文章的,在這裏記錄下個人一些學習筆記,一是爲了幫助我更深刻地理解設計模式,二同時能夠給一些初學設計模式的朋友一些參考。首先我介紹的是設計模式中比較簡單的一個模式——單例模式(由於這裏只牽涉到一個類)數據庫

2、單例模式的介紹

說到單例模式,你們第一反應應該就是——什麼是單例模式?,從「單例」字面意思上理解爲——一個類只有一個實例,因此單例模式也就是保證一個類只有一個實例的一種實現方法罷了(設計模式其實就是幫助咱們解決實際開發過程當中的方法, 該方法是爲了下降對象之間的耦合度,然而解決方法有不少種,因此前人就總結了一些經常使用的解決方法爲書籍,從而把這本書就稱爲設計模式),下面給出單例模式的一個官方定義: 確保一個類只有一個實例,並提供一個全局訪問點。 爲了幫助你們更好地理解單例模式,你們能夠結合下面的類圖來進行理解,以及後面也會剖析單例模式的實現思路:設計模式

3、爲何會有單例模式


看完單例模式的介紹,天然你們都會有這樣一個疑問——爲何要有單例模式的?它在什麼狀況下使用的?從單例模式的定義中咱們能夠看出——單例模式的使用天然是當咱們的系統中某個對象只須要一個實例的狀況,例如:操做系統中只能有一個任務管理器,操做文件時,同一時間內只容許一個實例對其操做等,既然現實生活中有這樣的應用場景,天然在軟件設計領域必須有這樣的解決方案了(由於軟件設計也是現實生活中的抽象),因此也就有了單例模式了。安全

4、剖析單例模式的實現思路


瞭解完了一些關於單例模式的基本概念以後,下面就爲你們剖析單例模式的實現思路的,由於在我本身學習單例模式的時候,咋一看單例模式的實現代碼確實很簡單,也很容易看懂,可是我仍是以爲它很陌生(這個多是看的少的,或者本身在寫代碼中也用的少的緣故),並且內心總會這樣一個疑問——爲何前人會這樣去實現單例模式的呢?他們是如何思考的呢?後面通過本身的琢磨也就慢慢理清楚單例模式的實現思路了,而且此時也再也不以爲單例模式模式的,下面就分享個人一個剖析過程的:多線程

咱們從單例模式的概念(確保一個類只有一個實例,並提供一個訪問它的全局訪問點)入手,能夠把概念進行拆分爲兩部分:函數

(1)確保一個類只有一個實例;工具

(2)提供一個訪問它的全局訪問點;性能

下面經過採用兩人對話的方式來幫助你們更快掌握分析思路:學習

菜鳥:怎樣確保一個類只有一個實例了?spa

老鳥:那就讓我幫你分析下,你建立類的實例會想到用什麼方式來建立的呢?操作系統

新手:用new關鍵字啊,只要new下就建立了該類的一個實例了,以後就可使用該類的一些屬性和實例方法了

老鳥:那你想過爲何可使用new關鍵字來建立類的實例嗎?

菜鳥:這個還有條件的嗎?........., 哦,我想起來了,若是類定義私有的構造函數就不能在外界經過new建立實例了(注:有些初學者就會問,有時候我並無在類中定義構造函數爲何也可使用new來建立對象,那是由於編譯器在背後作了手腳了,當編譯器看到咱們類中沒有定義構造函數,此時編譯器會幫咱們生成一個公有的無參構造函數)

老鳥:不錯,回答的很對,這樣你的疑惑就獲得解答了啊

菜鳥:那我要在哪裏建立類的實例了?

老鳥:你傻啊,固然是在類裏面建立了(注:這樣定義私有構造函數就是上面的一個思考過程的,要建立實例,天然就要有一個變量來保存該實例把,因此就有了私有變量的聲明,可是實現中是定義靜態私有變量,朋友們有沒有想過——這裏爲何定義爲靜態的呢?對於這個疑問的解釋爲:每一個線程都有本身的線程棧,定義爲靜態主要是爲了在多線程確保類有一個實例)

菜鳥:哦,如今徹底明白了,可是我還有另外一個疑問——如今類實例建立在類內部,那外界如何得到該的一個實例來使用它了?

老鳥:這個,你能夠定義一個公有方法或者屬性來把該類的實例公開出去了(注:這樣就有了公有方法的定義了,該方法就是提供方法問類的全局訪問點)

經過上面的分析,相信你們也就很容易寫出單例模式的實現代碼了,下面就看看具體的實現代碼(看完以後你會驚訝道:真是這樣的!):

/// <summary>
/// 單例模式的實現
/// </summary>
public class Singleton
{
    // 定義一個靜態變量來保存類的實例
    private static Singleton uniqueInstance;
    // 定義私有構造函數,使外界不能建立該類實例
    private Singleton()
    {
    }
    /// <summary>
    /// 定義公有方法提供一個全局訪問點,同時你也能夠定義公有屬性來提供全局訪問點
    /// </summary>
    /// <returns></returns>
    public static Singleton GetInstance()
    {
        // 若是類的實例不存在則建立,不然直接返回
        if (uniqueInstance == null)
        {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

上面的單例模式的實如今單線程下確實是完美的,然而在多線程的狀況下會獲得多個Singleton實例,由於在兩個線程同時運行GetInstance方法時,此時兩個線程判斷(uniqueInstance ==null)這個條件時都返回真,此時兩個線程就都會建立Singleton的實例,這樣就違背了咱們單例模式初衷了,既然上面的實現會運行多個線程執行,那咱們對於多線程的解決方案天然就是使GetInstance方法在同一時間只運行一個線程運行就行了,也就是咱們線程同步的問題了(對於線程同步你們也能夠參考我線程同步的文章),具體的解決多線程的代碼以下:

/// <summary>
/// 單例模式的實現
/// </summary>
public class Singleton
{
    // 定義一個靜態變量來保存類的實例
    private static Singleton uniqueInstance;
    // 定義一個標識確保線程同步
    private static readonly object locker = new object();
    // 定義私有構造函數,使外界不能建立該類實例
    private Singleton()
    {
    }
    /// <summary>
    /// 定義公有方法提供一個全局訪問點,同時你也能夠定義公有屬性來提供全局訪問點
    /// </summary>
    /// <returns></returns>
    public static Singleton GetInstance()
    {
        // 當第一個線程運行到這裏時,此時會對locker對象 "加鎖",
        // 當第二個線程運行該方法時,首先檢測到locker對象爲"加鎖"狀態,該線程就會掛起等待第一個線程解鎖
        // lock語句運行完以後(即線程運行完以後)會對該對象"解鎖"
        lock (locker)
        {
            // 若是類的實例不存在則建立,不然直接返回
            if (uniqueInstance == null)
            {
                uniqueInstance = new Singleton();
            }
        }
        return uniqueInstance;
    }
}

上面這種解決方案確實能夠解決多線程的問題,可是上面代碼對於每一個線程都會對線程輔助對象locker加鎖以後再判斷實例是否存在,對於這個操做徹底沒有必要的,由於當第一個線程建立了該類的實例以後,後面的線程此時只須要直接判斷(uniqueInstance==null)爲假,此時徹底不必對線程輔助對象加鎖以後再去判斷,因此上面的實現方式增長了額外的開銷,損失了性能,爲了改進上面實現方式的缺陷,咱們只須要在lock語句前面加一句(uniqueInstance==null)的判斷就能夠避免鎖所增長的額外開銷,這種實現方式咱們就叫它 「雙重鎖定」,下面具體看看實現代碼的:

/// <summary>
/// 單例模式的實現
/// </summary>
public class Singleton
{
    // 定義一個靜態變量來保存類的實例
    private static Singleton uniqueInstance;
    // 定義一個標識確保線程同步
    private static readonly object locker = new object();
    // 定義私有構造函數,使外界不能建立該類實例
    private Singleton()
    {
    }
    /// <summary>
    /// 定義公有方法提供一個全局訪問點,同時你也能夠定義公有屬性來提供全局訪問點
    /// </summary>
    /// <returns></returns>
    public static Singleton GetInstance()
    {
        // 當第一個線程運行到這裏時,此時會對locker對象 "加鎖",
        // 當第二個線程運行該方法時,首先檢測到locker對象爲"加鎖"狀態,該線程就會掛起等待第一個線程解鎖
        // lock語句運行完以後(即線程運行完以後)會對該對象"解鎖"
        // 雙重鎖定只須要一句判斷就能夠了
        if (uniqueInstance == null)
        {
            lock (locker)
            {
                // 若是類的實例不存在則建立,不然直接返回
                if (uniqueInstance == null)
                {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

5、C#中實現了單例模式的類


理解完了單例模式以後,菜鳥又接着問了:.NET FrameWork類庫中有沒有單例模式的實現呢?

通過查看,.NET類庫中確實存在單例模式的實現類,不過該類不是公開的,下面就具體看看該類的一個實現的(該類具體存在於System.dll程序集,命名空間爲System,你們能夠用反射工具Reflector去查看源碼的):

// 該類不是一個公開類
// 可是該類的實現應用了單例模式
internal sealed class SR
{
    private static SR loader;
    internal SR()
    {
    }
    // 主要是由於該類不是公有,因此這個所有訪問點也定義爲私有的了
    // 可是思想仍是用到了單例模式的思想的
    private static SR GetLoader()
    {
        if (loader == null)
        {
            SR sr = new SR();
            Interlocked.CompareExchange<SR>(ref loader, sr, null);
        }
        return loader;
    }
    // 這個公有方法中調用了GetLoader方法的
    public static object GetObject(string name)
    {
        SR loader = GetLoader();
        if (loader == null)
        {
            return null;
        }
        return loader.resources.GetObject(name, Culture);
    }
}

6、總結


到這裏,設計模式的單例模式就介紹完了,但願經過本文章你們能夠對單例模式有一個更深的理解,而且但願以前沒接觸過單例模式或以爲單例模式陌生的朋友看完以後會驚歎:原來如此!

以上內容摘抄自:http://learninghard.blog.51cto.com/6146675/1247003

 

補充:

另外一種單例模式:

//另外一種單例模式:
public class UserLoginInfo
{
    //實現Singleton模式,線程安全。
    private readonly static UserLoginInfo currentUserInfo = new UserLoginInfo();
    //提供全局訪問點
    public static UserLoginInfo CurrentUserInfo
    {
        get { return currentUserInfo; }
    }
    //阻止顯式實例化,但不能阻止反射方式調用。
    private UserLoginInfo()
    {
    }
    //公共變量
    public string UserName;
    public int UID;
    public bool UserType;
    //私有變量
    private static string userRole;
    //私有變量
    private static string password;
    //內部屬性
    internal string UserRole
    {
        get { return userRole; }
        set { userRole = value; }
    }
    //公共屬性
    public string Password
    {
        get { return password; }
        internal set { password = value; }
    }
}

單例模式優缺點:

主要優勢:一、提供了對惟一實例的受控訪問。二、因爲在系統內存中只存在一個對象,所以能夠節約系統資源,對於一些須要頻繁建立和銷燬的對象單例模式無疑能夠提升系統的性能。三、容許可變數目的實例。 主要缺點:一、因爲單利模式中沒有抽象層,所以單例類的擴展有很大的困難。二、單例類的職責太重,在必定程度上違背了「單一職責原則」。三、濫用單例將帶來一些負面問題,如爲了節省資源將數據庫鏈接池對象設計爲的單例類,可能會致使共享鏈接池對象的程序過多而出現鏈接池溢出;若是實例化的對象長時間不被利用,系統會認爲是垃圾而被回收,這將致使對象狀態的丟失。

相關文章
相關標籤/搜索