世界上本來沒有設計模式。用的人多了,也就成了設計模式。所以,我們不是嚴格按照它的定義去執行,可以根據自己的實際場景、需求去變通。領悟了其中的思想,實現屬於自己的設計模式。 你肯定有過這樣的體會。某某時候,聽人說起**模式。這麼牛逼,回去得看看。結果仔細一看原來自己早就是這麼用了,只是不知道它還有個這... ...
世界上本來沒有設計模式
。用的人多了,也就成了設計模式
。所以,我們不是嚴格按照它的定義去執行,可以根據自己的實際場景、需求去變通。領悟了其中的思想,實現屬於自己的設計模式
。
你肯定有過這樣的體會。某某時候,聽人說起**模式。這麼牛逼,回去得看看。結果仔細一看原來自己早就是這麼用了,只是不知道它還有個這麼高大上的名字。當然,專業的名字方便我們業內交流和教學,對技術的發展和傳播起著重要的作用。
廢話不多說,和我一起來學習這些高大上的術語吧。本系列《設計模式學習》,通過對傳統面向對象編程語言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 = "[email protected]";
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