1、 什么是單例模式?
單例模式的意思就是只有一個實例。單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例。這個類稱為單例類。
單例模式的要點(diǎn)如下:
(1)一個類只能有一個實例;
(2)自己創(chuàng)建這個實例;
(3)整個系統(tǒng)都要使用這個實例。
單例模式的示意圖如下所示:

在上面的對象圖中,有一個“單例對象”,而“客戶甲”、“客戶乙”和“客戶丙”是單例對象的三個客戶對象。可以看到,所有的客戶對象共享一個單例對象,而且從單例對象到自身的連接線可以看出,單例對象持有對自己的引用。
2、 單例模式的應(yīng)用場景
單例模式作為一種比較常見的設(shè)計模式,作用如下:
(1)控制資源的使用,通過線程同步來控制資源的并發(fā)訪問;
(2)控制實例產(chǎn)生的數(shù)量,達(dá)到節(jié)約資源的目的;
(3)作為通信媒介使用,也就是數(shù)據(jù)共享,它可以在不建立直接關(guān)聯(lián)的條件下,讓多個不相關(guān)的兩個線程或者進(jìn)程之間實現(xiàn)通信。
單例模式常見的參考使用如下:
(1)資源管理:在計算機(jī)系統(tǒng)中,需要管理的資源包括軟件外部資源,譬如每臺計算機(jī)可以有若干個打印機(jī),但只能有一個Printer Spooler, 以避免兩個打印作業(yè)同時輸出到打印機(jī)中。每臺計算機(jī)可以有若干傳真卡,但是只應(yīng)該有一個軟件負(fù)責(zé)管理傳真卡,以避免出現(xiàn)兩份傳真作業(yè)同時傳到傳真卡中的情 況。每臺計算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調(diào)用。這些資源管理器構(gòu)件必須只有一個實例
(2)回收站等應(yīng)用:在整個視窗系統(tǒng)中,Windows的回收站只能有一個實例,整個系統(tǒng)都使用這個惟一的實例,而且回收站自行提供自己的實例。因此,回收站是單例模式的應(yīng)用。
3、 單例模式的兩種形式
根據(jù)在建立單例對象的時間不同,可以形象的將單例模式分為兩種實現(xiàn)形式:“餓漢式”和“懶漢式”。
3.1 “餓漢式”單例模式實現(xiàn)
“餓漢式”是在不管你用的用不上,一開始就建立這個單例對象,例如如下Java代碼實現(xiàn)了餓漢式的單例模式:
package singleton;

/**
* 餓漢式單例模式
* @author AmigoXie
*/
public class Singleton {
private Singleton() {
System.out.println("構(gòu)造器");
}

//在自己內(nèi)部定義自己一個實例,注意這是private 只供內(nèi)部調(diào)用
private static Singleton instance = new Singleton();
//這里提供了一個供外部訪問本class的靜態(tài)方法,可以直接訪問
public static Singleton getInstance() {
return instance;
}

public static void main(String[] args) {
System.out.println("測試餓漢式單例模式");
Singleton.getInstance();
}
} 該例的運(yùn)行結(jié)果如下所示:
構(gòu)造器
測試餓漢式單例模式
由此可知在調(diào)用單例對象的靜態(tài)方法getInstance()之前,構(gòu)造器Singleton()已被調(diào)用。
3.2 “懶漢式”單例模式實現(xiàn)
“懶漢式”是在你真正用到的時候才去建這個單例對象。,例如如下Java代碼實現(xiàn)了懶漢式的單例模式:
package singleton;


/** *//**
* 懶漢式單例模式
* @author AmigoXie
*/

public class Singleton2
{

private Singleton2()
{
System.out.println("構(gòu)造器");
}

private static Singleton2 instance = null;


public static synchronized Singleton2 getInstance()
{
//這個方法比上面有所改進(jìn),不用每次都進(jìn)行生成對象,只是第一次使用時生成實例

if (instance == null)
{
instance = new Singleton2();
}
return instance;
}


public static void main(String[] args)
{
System.out.println("測試懶漢式單例模式");
Singleton2.getInstance();
}
}
該例的運(yùn)行結(jié)果如下所示:
測試懶漢式單例模式
構(gòu)造器
從運(yùn)行的打印結(jié)果表名,在getInstance()獲取實例方法被調(diào)用時,構(gòu)造器才被調(diào)用。
4、 在JavaScript中使用單例模式
在JavaScript中,單例(Singleton)模式是最基本又最有用的模式之一。這種模式提供了一種將代碼組織為一個邏輯單元的手段,這個邏輯單元中的代碼可以通過單一的變量進(jìn)行訪問。確保單例對象只有一份實例,你就可以確信自己的所有代碼使用的都是同樣的全局資源。
單例類在JavaScript中用途廣泛:
(1)可以用來劃分命名空間,以減少網(wǎng)頁中全局變量的數(shù)量;
(2)可以在一種名為分支的技術(shù)中用來封裝瀏覽器之間的差異;
(3)可以借助于單例模式,將代碼組織得更為一致,從而使代碼更容易閱讀和維護(hù)。
4.1 單例的基本結(jié)構(gòu)
最基本的單例實際上是一個對象字面值,它將一批有一定關(guān)聯(lián)的方法和屬性組織在一起。例如如下JavaScript代碼:

var Singleton =
{
attribute1: true;
attribute2: 10

method1: function()
{
},

method2: function()
{
}
};
這些成員可以通過Singleton加圓點(diǎn)運(yùn)算符來訪問:
Singleton.attribute1 = false;
var total = Singleton. attribute2 + 5;
var result = Singleton.method1();
對象字面值只是用以創(chuàng)建單例的方法之一,后面介紹的那些方法所創(chuàng)建的單體看起來更像其他面向?qū)ο笳Z言中的單例類。另外,并非所有對象字面值都是單體,如果它只是用來模仿關(guān)聯(lián)數(shù)組或容納數(shù)據(jù)的話,那顯然不是單例。但如果它是用來組織一批相關(guān)方法和屬性的話,那就可能是單例,其區(qū)別主要在于設(shè)計者的意圖。
4.2 創(chuàng)建擁有私有成員的單例
4.2.1 使用下劃線表示法
在單例對象內(nèi)創(chuàng)建類的私有成員的最簡單、最直截了當(dāng)?shù)姆椒ㄊ鞘褂孟聞澗€表示法(在JavaScript業(yè)界,如果變量和方法是使用下劃線,則表示該變量和方法是私有方法,只允許內(nèi)部調(diào)用,第三方不應(yīng)該去調(diào)用)。參考實例如下:

GiantCorp.DataParser =
{
// 私有方法

_stripWhitespace: function(str)
{
return str.replace(/\s+/, '');
},

_stringSplit: function(str, delimiter)
{
return str.split(delimiter);
},

// 公用方法

stringToArray: function(str, delimiter, stripWS)
{

if (stripWS)
{
str = this._stripWhitespace(str);
}
var outputArray = this._stringSplit(str, delimiter);
return outputArray;
}
};
在如上代碼中,stringToArray方法中使用this訪問單體中的其他方法,這是訪問單體中其他成員或方法的最簡便的方法。但這樣做有一點(diǎn)風(fēng)險,因為this并不一定指向GiantCorp.DataParser例如,如果把某個方法用作事件監(jiān)聽器,那么其中的this指向的是window對象,因此大多數(shù)JavaScript庫都會為事件關(guān)聯(lián)進(jìn)行作用域校正,例如在這里使用GiantCorp.DataParser比使用this更為安全。
4.2.2使用閉包
在單例對象中創(chuàng)建私有成員的第二種方法是借助閉包。因為單例只會被實例化一次,所以不必?fù)?dān)心自己在構(gòu)造函數(shù)中聲明了多少成員。每個方法和屬性都只會被創(chuàng)建一次,所以可以把它們聲明在構(gòu)造函數(shù)內(nèi)部(因此也就位于同一個閉包中)。
使用閉包創(chuàng)建擁有私有成員的單例類的實例如下:

MyNamespace.Ssingleton = (function()
{
// 私有成員
var privateAttribute1 = false;
var privateAttribute2 = [1, 2, 3];

function privateMethod1()
{

}


function privateMethod2()
{

}

return
{
// 公有成員
publicAttribute1: true;
publicAttribute2: 10,

publicMethod1: function()
{

},

publicMethod2: function()
{

}
};
})();
這種單例模式又稱為模塊模式,指的是它可以把一批相關(guān)方法和屬性組織為模塊并起到劃分命名空間的作用。
使用該種方式改造4.2.1中的實例,參考代碼如下:

GiantCorp.DataParser = (function()
{
var whiteSpaceRegex = /\s+/;
// 私有方法

function stripWhitespace(str)
{
return str.replace(whiteSpaceRegex, '');
}


function stringSplit(str, delimiter)
{
return str.split(delimiter);
},


return
{
// 公用方法

stringToArray: function(str, delimiter, stripWS)
{

if (stripWS)
{
str = stripWhitespace(str);
}
var outputArray = stringSplit(str, delimiter);
return outputArray;
}
};
})();
將私有成員放在閉包中可以確保其不會在單例對象之外被使用,因此開發(fā)人員可以自由的改變對象的實現(xiàn)細(xì)節(jié),而不會殃及別人的代碼。還可以使用這種辦法對數(shù)據(jù)進(jìn)行保護(hù)和封裝。
4.3 在JavaScript中實現(xiàn)“懶漢式”單例模式
在如上的代碼中,單例對象都是在腳本加載時被創(chuàng)建出來。對于資源密集型或配置開銷甚大的單例,更合理的是使用3.2小節(jié)中提到的“懶漢式”單例實現(xiàn)。這種實現(xiàn)方式的特別之處在于,對它們的訪問必須借助于一個靜態(tài)方法,例如調(diào)用單例類的getInstance()方法獲得對象實例。
參考實現(xiàn)代碼如下:

MyNamespace.Singleton = (function()
{
// 定義一個私有的屬性,該屬性用于儲存單例對象
var uniqueInstance;

function constructor()
{
// 將單態(tài)操作放在這里

}


return
{

getInstance: function()
{

if (!uniqueInstance)
{
uniqueInstance = constructor();
}
return uniqueInstance;
}
}
})();
將一個單例轉(zhuǎn)換為懶漢式加載方式后,必須對調(diào)用它的代碼進(jìn)行修改,例如之前調(diào)用:
MyNamespace.Singleton.publicMethod1();
應(yīng)該改成如下代碼:
MyNamespace.Singleton.getInstance().publicMethod1();
如果覺得命名空間名稱太長,可以創(chuàng)建一個別名來簡化它。
4.4 使用單例模式實現(xiàn)分支
分支是一種用來把瀏覽器之間的差異封裝到運(yùn)行期間進(jìn)行設(shè)置的動態(tài)方法中的技術(shù)。例如,假設(shè)我們需要一個創(chuàng)建XHR對象的方法,這種XHR對象在大多數(shù)瀏覽器中是XMLHttpRequest對象的實例,但在IE早期版本中是某種ActiveX類的實例,這樣一種方法通常會進(jìn)行某種瀏覽器嗅探或?qū)ο筇綔y。如果不用分支技術(shù),那么每次調(diào)用這個方法時,所有這些瀏覽器嗅探代碼都要再次運(yùn)行。如果該方法調(diào)用頻繁,將會嚴(yán)重影響效率。
要實現(xiàn)獲取不同瀏覽器的XHR對象的功能,參考實現(xiàn)代碼的實現(xiàn)步驟如下:
(1)判斷有多少個分支(有3個),這些分支按其返回的XHR對象類型命名,這三個分支都有一個名為createXhrObject()的方法,該方法返回一個可以執(zhí)行異步請求的新對象;
(2)根據(jù)條件將3個分支中某一分支的對象賦給那個變量,具體做法是逐一嘗試XHR對象,直到遇到一個當(dāng)前JavaScript環(huán)境所支持的對象為止。
參考代碼如下所示:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>使用單例模式實現(xiàn)JavaScript中的分支</title>
<script type="text/javascript">

var SimpleXhrFactory = (function()
{
// 三個分支

var standard =
{

createXhrObject: function()
{
alert("standard createXhrObject");
return new XMLHttpRequest();
}
};

var activeXNew =
{

createXhrObject: function()
{
alert("activeXNew createXhrObject");
return new ActiveXObject("Msxml2.XMLHTTP");
}
};

var activeXOld =
{

createXhrObject: function()
{
alert("activeXOld createXhrObject");
return new ActiveXObject("Microsoft.XMLHTTP");
}
};

// 為分支賦值
var testObject;

try
{
testObject = standard.createXhrObject();
return standard;

} catch(e)
{

try
{
testObject = activeXNew.createXhrObject();
return activeXNew;

} catch(e)
{

try
{
testObject = activeXOld.createXhrObject();
return activeXOld;

} catch(e)
{
throw new Error("在該瀏覽器環(huán)境中沒有發(fā)現(xiàn)XHR對象.");
}
}
}
})();

SimpleXhrFactory.createXhrObject();

</script>

</head>

<body>

</body>

</html>

用了分支技術(shù)后,所有的那些特性嗅探代碼都只會執(zhí)行一次,而不是沒生成一個對象執(zhí)行一次。這種技術(shù)適用于任何只有在運(yùn)行時才能確定具體實現(xiàn)的情況。
5、 單例模式的優(yōu)缺點(diǎn)
在JavaScript中使用單例模式的主要優(yōu)點(diǎn)如下:
(1)對代碼的組織作用:它將相關(guān)方法和屬性組織在一個不會被多次實例話的單例中,可以使代碼的調(diào)試和維護(hù)變得更輕松。描述性的命名空間還可以增強(qiáng)代碼的自我說明性。將方法包裹在單例中,可以防止它們被其它程序員誤改。
(2)單例模式的一些高級變體可以在開發(fā)周期的后期用于對腳本進(jìn)行優(yōu)化。
主要缺點(diǎn)如下:
(1)因為提供的是一種單點(diǎn)訪問,所以它有可能導(dǎo)致模塊間的強(qiáng)耦合。單體最好是留給定義命名空間和實現(xiàn)分支型方法這些用途,這些情況下,耦合不是什么問題;
(2)有時某些更高級的模式會更符合任務(wù)的需要。與“懶漢式”加載單例相比,虛擬代理能給予你對實例化方式更多的控制權(quán)。也可以用一個真正的對象工廠來取代分支型單例。
6、 參考文檔
(1)《JavaScript設(shè)計模式》 Ross Harmes,Dustin Dial著,謝廷晟 譯,人民郵電出版社出版
(2)《單例模式_百度百科》:
http://baike.baidu.com/view/1859857.htm
posted on 2011-11-07 13:14
阿蜜果 閱讀(3108)
評論(3) 編輯 收藏 所屬分類:
Javascript 、
Design Pattern