單例模式之所以這么叫,是因?yàn)樗拗埔粋€(gè)類只能有一個(gè)實(shí)例化對(duì)象。經(jīng)典的實(shí)現(xiàn)方式是,創(chuàng)建一個(gè)類,這個(gè)類包含一個(gè)方法,這個(gè)方法在沒(méi)有對(duì)象存在的情況下,將會(huì)創(chuàng)建一個(gè)新的實(shí)例對(duì)象。如果對(duì)象存在,這個(gè)方法只是返回這個(gè)對(duì)象的引用。
單例和靜態(tài)類不同,因?yàn)槲覀兛梢酝顺鰡卫某跏蓟瘯r(shí)間。通常這樣做是因?yàn)?,在初始化的時(shí)候需要一些額外的信息,而這些信息在聲明的時(shí)候無(wú)法得知。對(duì)于并不知曉對(duì)單例模式引用的代碼來(lái)講,單例模式?jīng)]有為它們提供一種方式可以簡(jiǎn)單的獲取單例模式。這是因?yàn)?,單例模式既不返回?duì)象也不返回類,它只返回一種結(jié)構(gòu)??梢灶惐乳]包中的變量不是閉包-提供閉包的函數(shù)域是閉包(繞進(jìn)去了)。
在JavaScript語(yǔ)言中, 單例服務(wù)作為一個(gè)從全局空間的代碼實(shí)現(xiàn)中隔離出來(lái)共享的資源空間是為了提供一個(gè)單獨(dú)的函數(shù)訪問(wèn)指針。
我們能像這樣實(shí)現(xiàn)一個(gè)單例:
var mySingleton = (function () {
// Instance stores a reference to the Singleton
var instance;
function init() {
// 單例
// 私有方法和變量
function privateMethod(){
console.log( "I am private" );
}
var privateVariable = "Im also private";
var privateRandomNumber = Math.random();
return {
// 共有方法和變量
publicMethod: function () {
console.log( "The public can see me!" );
},
publicProperty: "I am also public",
getRandomNumber: function() {
return privateRandomNumber;
}
};
};
return {
// 如果存在獲取此單例實(shí)例,如果不存在創(chuàng)建一個(gè)單例實(shí)例
getInstance: function () {
if ( !instance ) {
instance = init();
}
return instance;
}
};
})();
var myBadSingleton = (function () {
// 存儲(chǔ)單例實(shí)例的引用
var instance;
function init() {
// 單例
var privateRandomNumber = Math.random();
return {
getRandomNumber: function() {
return privateRandomNumber;
}
};
};
return {
// 總是創(chuàng)建一個(gè)新的實(shí)例
getInstance: function () {
instance = init();
return instance;
}
};
})();
// 使用:
var singleA = mySingleton.getInstance();
var singleB = mySingleton.getInstance();
console.log( singleA.getRandomNumber() === singleB.getRandomNumber() ); // true
var badSingleA = myBadSingleton.getInstance();
var badSingleB = myBadSingleton.getInstance();
console.log( badSingleA.getRandomNumber() !== badSingleB.getRandomNumber() ); // true
創(chuàng)建一個(gè)全局訪問(wèn)的單例實(shí)例 (通常通過(guò) MySingleton.getInstance()) 因?yàn)槲覀儾荒?至少在靜態(tài)語(yǔ)言中) 直接調(diào)用 new MySingleton() 創(chuàng)建實(shí)例. 這在JavaScript語(yǔ)言中是不可能的。
在四人幫(GoF)的書(shū)里面,單例模式的應(yīng)用描述如下:
關(guān)于第二點(diǎn),可以參考如下的實(shí)例,我們需要這樣編碼:
mySingleton.getInstance = function(){
if ( this._instance == null ) {
if ( isFoo() ) {
this._instance = new FooSingleton();
} else {
this._instance = new BasicSingleton();
}
}
return this._instance;
};
在這里,getInstance 有點(diǎn)類似于工廠方法,我們不需要去更新每個(gè)訪問(wèn)單例的代碼。FooSingleton可以是BasicSinglton的子類,并且實(shí)現(xiàn)了相同的接口。
為什么對(duì)于單例模式來(lái)講,延遲執(zhí)行執(zhí)行這么重要?
在c++代碼中,單例模式將不可預(yù)知的動(dòng)態(tài)初始化順序問(wèn)題隔離掉,將控制權(quán)返回給程序員。
區(qū)分類的靜態(tài)實(shí)例和單例模式很重要:盡管單例模式可以被實(shí)現(xiàn)成一個(gè)靜態(tài)實(shí)例,但是單例可以懶構(gòu)造,在真正用到之前,單例模式不需要分配資源或者內(nèi)存。
如果我們有個(gè)靜態(tài)對(duì)象可以被直接初始化,我們需要保證代碼總是以同樣的順序執(zhí)行(例如 汽車(chē)需要輪胎先初始化)當(dāng)你有很多源文件的時(shí)候,這種方式?jīng)]有可擴(kuò)展性。
單例模式和靜態(tài)對(duì)象都很有用,但是不能濫用-同樣的我們也不能濫用其它模式。
在實(shí)踐中,當(dāng)一個(gè)對(duì)象需要和另外的對(duì)象進(jìn)行跨系統(tǒng)協(xié)作的時(shí)候,單例模式很有用。下面是一個(gè)單例模式在這種情況下使用的例子:
var SingletonTester = (function () {
// options: an object containing configuration options for the singleton
// e.g var options = { name: "test", pointX: 5};
function Singleton( options ) {
// set options to the options supplied
// or an empty object if none are provided
options = options || {};
// set some properties for our singleton
this.name = "SingletonTester";
this.pointX = options.pointX || 6;
this.pointY = options.pointY || 10;
}
// our instance holder
var instance;
// an emulation of static variables and methods
var _static = {
name: "SingletonTester",
// Method for getting an instance. It returns
// a singleton instance of a singleton object
getInstance: function( options ) {
if( instance === undefined ) {
instance = new Singleton( options );
}
return instance;
}
};
return _static;
})();
var singletonTest = SingletonTester.getInstance({
pointX: 5
});
// Log the output of pointX just to verify it is correct
// Outputs: 5
console.log( singletonTest.pointX );
盡管單例模式有著合理的使用需求,但是通常當(dāng)我們發(fā)現(xiàn)自己需要在javascript使用它的時(shí)候,這是一種信號(hào),表明我們可能需要去重新評(píng)估自己的設(shè)計(jì)。
這通常表明系統(tǒng)中的模塊要么緊耦合要么邏輯過(guò)于分散在代碼庫(kù)的多個(gè)部分。單例模式更難測(cè)試,因?yàn)榭赡苡卸喾N多樣的問(wèn)題出現(xiàn),例如隱藏的依賴關(guān)系,很難去創(chuàng)建多個(gè)實(shí)例,很難清理依賴關(guān)系,等等。
要想進(jìn)一步了解關(guān)于單例的信息,可以讀讀 Miller Medeiros 推薦的這篇非常棒的關(guān)于單例模式以及單例模式各種各樣問(wèn)題的文章,也可以看看這篇文章的評(píng)論,這些評(píng)論討論了單例模式是怎樣增加了模塊間的緊耦合。我很樂(lè)意去支持這些推薦,因?yàn)檫@兩篇文章提出了很多關(guān)于單例模式重要的觀點(diǎn),而這些觀點(diǎn)是很值得重視的。