面向對象編程思想(1)--單例模式


世界上本來沒有設計模式。用的人多了,也就成了設計模式。所以,我們不是嚴格按照它的定義去執行,可以根據自己的實際場景、需求去變通。領悟了其中的思想,實現屬於自己的設計模式
你肯定有過這樣的體會。某某時候,聽人說起**模式。這么牛逼,回去得看看。結果仔細一看原來自己早就是這么用了,只是不知道它還有個這么高大上的名字。當然,專業的名字方便我們業內交流和教學,對技術的發展和傳播起着重要的作用。

廢話不多說,和我一起來學習這些高大上的術語吧。本系列《設計模式學習》,通過對傳統面向對象編程語言C#和函數為第一等的元素的javascript語言來對比學習加深對設計模式的領悟和運用。

定義

單例模式
個人理解:只能存在一個實例
官方解釋:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

C#代碼示例

示例1

public static class Singleton
{
   //TODO
}

表激動,它確實不是我們平時使用的單例模式。它只是個靜態對象。但是,我覺得也是可以當成單例來使用的。
當然,肯定有它的不足,不然也不會去搞個單例模式了。

  • 致命的缺點,不能繼承類也不能實現接口。
  • 靜態類中所有方法、字段必須是靜態的。
  • 你無法控制它的初始化。
  • 靜態類我們一般都是用來編寫和業務無關的基礎方法、擴展方法。而單例類是一個實例類,一般和業務相關。

示例2

public class Singleton
{
    public static Singleton singleton = new Singleton();   
}
Console.WriteLine(Singleton.singleton.Equals(Singleton.singleton));//true

其實它是個假單例

Singleton s1 = new Singleton();
Singleton s2 = new Singleton();
Console.WriteLine(s1.Equals(s2));//false

且有缺點

  • 在類被加載的時候就自動初始化了singleton
  • singleton應該被定義為只讀屬性

示例3

public class Singleton
{
    public static readonly Singleton singleton = new Singleton();//自讀字段
    private Singleton()//禁止初始化
    {
    }
}

這是一個比較簡單的單例,但是自動化初始變量還是存在

示例4

public class Singleton
{
    public static Singleton singleton = null;

    public static Singleton GetSingleton()
    {
        if (singleton == null)
        {
            singleton = new Singleton();
        }
        return singleton;
    }
    private Singleton()//禁止初始化
    {
    }
}

如此一來,我們就可以在調用GetSingleton方法的時候再去實例話了。注意:實例化之后singleton變量值不能再被GC回收了,因為它是個靜態變量。
如此就算完事了嗎?不,如果多線程同時執行的時候還是會出現多個實例。

public class Singleton
{
    public static Singleton singleton = null;

    public static Singleton GetSingleton()
    {
        if (singleton == null) //線程二執行到這里singleton == null為true,會繼續下面實例Singleton
        {
           //線程一執行到這里
            Thread.Sleep(1000);//假設這還有段耗時邏輯(也可以理解並發極限)
            singleton = new Singleton();
        }
        return singleton;
    }
    private Singleton()//禁止初始化
    {
    }
}

所以還需要繼續改進

示例5

public class Singleton
{
    public static Singleton singleton = null;
    private static object obj = new object();

    public static Singleton GetSingleton()
    {
        if (singleton == null) //下面有鎖了為什么還要判斷,因為鎖會阻塞線程。而singleton被實例化后這個判斷永遠為false,不在需要鎖。
        {
            lock (obj)
            {
                //這里代碼只可能存在一個線程同時到達
                if (singleton == null)
                {
                    Thread.Sleep(1000);
                    singleton = new Singleton();
                } 
            } 
        }
        return singleton;
    }
    private Singleton()//禁止初始化
    {
    }
}

這就是我們常見的單例類代碼了。當然你也可以改成讀取屬性的方式。但區別不大。

public class Singleton
{
    private static Singleton singleton = null;
    private static object obj = new object(); 
    public static Singleton Instance
    {
        get
        {
            if (singleton == null)
            {
                lock (obj)
                {
                    if (singleton == null)
                    {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        } 
    }
    private Singleton()//禁止初始化
    {
    }
}

C#使用場景

上面用了那么多的筆墨分析單例模式的使用,可是我們在什么場景下使用單例呢?
最典型的就是配置文件的讀取,通常我們的配置文件是在程序第一次啟動的時候讀取,運行中是不允許修改配置文件的。

public class ConfigInfo
{
    private static ConfigInfo singleton = null;
    private static object obj = new object();
    public static ConfigInfo Instance
    {
        get
        {
            if (singleton == null)
            {
                lock (obj)
                {
                    if (singleton == null)
                    {
                        singleton = new ConfigInfo(); 
                        //從配置文件讀取並賦值
                        singleton.Email = "zhaopeiym@163.com";
                        singleton.EmailUser = "農碼一生";
                        singleton.EmailPass = "***********";
                    }
                }
            }
            return singleton;
        }
    }

    public string Email { get; private set; }
    public string EmailUser { get; private set; }
    public string EmailPass { get; private set; } 

    private ConfigInfo()//禁止初始化
    {
    }
}

調用

var emailInfo = ConfigInfo.Instance;
EmailSend(emailInfo.Email,emailInfo.EmailUser,emailInfo.EmailPass);

好了,C#中的單例模式大概就這樣了。

JS代碼示例

js和C#是不同的,一個是"無類"語言,一個是傳統的面向對象語言。而在js中的單例就比較簡單了。比如我們熟悉的window對象。
那么我們怎么在js中實現自己的單例模式呢?方法很多,先來個簡單的:

示例1

var Singleton = {
    name: "農碼一生",
    getName: function () {
        return this.name;
    }  
}

這就是一個最簡單的單例,通過字面量創建一個對象。看着是不是非常像C#中的靜態類?但是,它不存在靜態類中的缺點。
繼承毫無壓力:

var Person = {
    age: 27
}

var Me = Person;
Me.name = "農碼一生";
Me.getName = function () {
    return this.name;
}
Me.getAge = function () {
    return this.age;
}

雖然如此,但它並不完美。按理說字段不應該被外界隨意修改的。可是js“無類”,更別說私有字段了。幸運的是js中有無處不在的閉包。
示例2

var Singleton = (function () {
    var name = "農碼一生";   
    return {
        getName: function () {
            return name;
        }
    }
})();

如此一來,我們就實現了一個單例模式。經過前面對C#單例的分析,我們希望在使用的時候才去實例話對象怎么辦?(且不要小看這個惰性加載,在實際開發中作用可大着呢。)
示例3

var Singleton = (function () {

    var Person = function () {
        this.name = "農碼一生";
    }
    Person.prototype.getName = function () {
        return this.name;
    };
    var instance;
    return {
        getInstance: function () {
            if (!instance) {
                instance = new Person();
            }
            return instance;
        }
    }
})();

var person1 = Singleton.getInstance();
var person2 = Singleton.getInstance();
console.log(person1 === person2);//true

這算是js中比較標准的單例模式了。可能有同學會問,之前C#的時候我記得加了lock鎖的啊。這里怎么就算比較標准了呢。不要忘記,js天生的單線程,后台天生的多線程。這就是區別。
為了職責的單一,我應該改寫成
示例4

var Person = function () {
    this.name = "農碼一生";
}
Person.prototype.getName = function () {
    return this.name;
};
    
var Singleton = (function () {
    var instance;
    return {
        getInstance: function () {
            return instance ||  (instance = new Person(););//簡化if判斷
        }
    }
})();

我們很多時候都會使用到單例,那我們可否把一個對象變成單例的過程抽象出來呢。如下:
示例5

//通用的創建單例對象的方法
var getSingle = function (obj) {
    var instance;
    return function () {
        return instance || (instance = new obj());
    }
};

var PersonA = function () {
    this.name = "農碼一生";
}

var PersonB = function () {
    this.name = "農碼愛妹子";
} 

var singlePersonA = getSingle(PersonA);//獲取PersonA的單例
var singlePersonB = getSingle(PersonB);//獲取PersonB的單例
var a1 = singlePersonA();
var a2 = singlePersonA();
var a3 = singlePersonB();
var a4 = singlePersonB();
console.log(a1 === a2);//true
console.log(a3 === a4);//true
console.log(a1 === a3);//false 

有沒有頭暈暈的,習慣就好了。你會說,我直接用最開始的全局變量字面量對象得了,可你不要忘記會造成變量名的污染。

JS使用場景

我們在做Tab也切換的時候就可以用到單例模式。在此,我們做個非單例和單例的比較
示例6非單例:

//獲取tab1的html數據
var getTab1Html = function () {
    this.url = "/tab/tab1.json";
    //$.get(this.url, function (data) {
    //    //這里獲取請求到的數據,然后加載到tab頁面
    //}, "json");
    console.log("執行");
}

var getTab2Html = function () {
    this.url = "/tab/tab2.json";
    //$.get(this.url, function (data) {
    //    //這里獲取請求到的數據,然后加載到tab頁面
    //}, "json");
    console.log("執行");
} 

//點擊tab1的時候加載tab1的數據
$("#tab1").on("click", function () {
    getTab1Html();
})
$("#tab2").on("click", function () {
    getTab2Html();
})

我們發現沒點擊一次tab的時候會請求一次后台數據,然后加載頁面。這是不是有點傻。正確的姿勢應該是第一次點擊的時候加載,后面不在請求加載。那么我們就可以使用單例模式了。
示例7單例:

//獲取tab1的html數據
var getTab1Html = function () {
    this.url = "/tab/tab1.json";
    //$.get(this.url, function (data) {
    //    //這里獲取請求到的數據,然后加載到tab頁面
    //}, "json");
    console.log("執行");
}

var getTab2Html = function () {
    this.url = "/tab/tab2.json";
    //$.get(this.url, function (data) {
    //    //這里獲取請求到的數據,然后加載到tab頁面
    //}, "json");
    console.log("執行");
} 

var loadTab1 = getSingle(getTab1Html);
var loadTab2 = getSingle(getTab2Html);

//點擊tab1的時候加載tab1的數據
$("#tab1").on("click", function () {
    loadTab1();
})
$("#tab2").on("click", function () {
    loadTab2();
})

此時,我們無論點擊多少此tab。它也只會在第一次點擊的時候請求加載頁面數據了。
 

注意:

  • JS中不建議使用全局變量來達到單例的效果
    • 其一,會引起變量名的全局污染
    • 其二,不能惰性加載。
  • C#中不建議使用靜態類來達到單例的效果
    • 其一,不能繼承類和接口
    • 其二,內部變量和方法必須靜態。
  • 單例模式中實例變量要慎用。因為一個單例很可能被多處操作(修改了變量),從而影響的預期效果。
     

設計模式之所以能成為設計模式,也是在不斷嘗試、改進后得到的最佳實踐而已。所以,我們不需要生搬硬套,適合的才是最好的。在此,關於單例模式的學習到此結束。謝謝您的閱讀。
本文已同步至索引目錄:《設計模式學習》
本文demo:https://github.com/zhaopeiym/BlogDemoCode


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM