單例模式之所以這么叫,是因為它限制一個類只能有一個實例化對象。經(jīng)典的實現(xiàn)方式是,創(chuàng)建一個類,這個類包含一個方法,這個方法在沒有對象存在的情況下,將會創(chuàng)建一個新的實例對象。如果對象存在,這個方法只是返回這個對象的引用。
單例和靜態(tài)類不同,因為我們可以退出單例的初始化時間。通常這樣做是因為,在初始化的時候需要一些額外的信息,而這些信息在聲明的時候無法得知。對于并不知曉對單例模式引用的代碼來講,單例模式?jīng)]有為它們提供一種方式可以簡單的獲取單例模式。這是因為,單例模式既不返回對象也不返回類,它只返回一種結(jié)構(gòu)??梢灶惐乳]包中的變量不是閉包-提供閉包的函數(shù)域是閉包(繞進去了)。
在JavaScript語言中, 單例服務(wù)作為一個從全局空間的代碼實現(xiàn)中隔離出來共享的資源空間是為了提供一個單獨的函數(shù)訪問指針。
我們能像這樣實現(xiàn)一個單例:
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 {
// 如果存在獲取此單例實例,如果不存在創(chuàng)建一個單例實例
getInstance: function () {
if ( !instance ) {
instance = init();
}
return instance;
}
};
})();
var myBadSingleton = (function () {
// 存儲單例實例的引用
var instance;
function init() {
// 單例
var privateRandomNumber = Math.random();
return {
getRandomNumber: function() {
return privateRandomNumber;
}
};
};
return {
// 總是創(chuàng)建一個新的實例
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)建一個全局訪問的單例實例 (通常通過 MySingleton.getInstance()) 因為我們不能(至少在靜態(tài)語言中) 直接調(diào)用 new MySingleton() 創(chuàng)建實例. 這在JavaScript語言中是不可能的。
在四人幫(GoF)的書里面,單例模式的應(yīng)用描述如下:
關(guān)于第二點,可以參考如下的實例,我們需要這樣編碼:
mySingleton.getInstance = function(){
if ( this._instance == null ) {
if ( isFoo() ) {
this._instance = new FooSingleton();
} else {
this._instance = new BasicSingleton();
}
}
return this._instance;
};
在這里,getInstance 有點類似于工廠方法,我們不需要去更新每個訪問單例的代碼。FooSingleton可以是BasicSinglton的子類,并且實現(xiàn)了相同的接口。
為什么對于單例模式來講,延遲執(zhí)行執(zhí)行這么重要?
在c++代碼中,單例模式將不可預(yù)知的動態(tài)初始化順序問題隔離掉,將控制權(quán)返回給程序員。
區(qū)分類的靜態(tài)實例和單例模式很重要:盡管單例模式可以被實現(xiàn)成一個靜態(tài)實例,但是單例可以懶構(gòu)造,在真正用到之前,單例模式不需要分配資源或者內(nèi)存。
如果我們有個靜態(tài)對象可以被直接初始化,我們需要保證代碼總是以同樣的順序執(zhí)行(例如 汽車需要輪胎先初始化)當你有很多源文件的時候,這種方式?jīng)]有可擴展性。
單例模式和靜態(tài)對象都很有用,但是不能濫用-同樣的我們也不能濫用其它模式。
在實踐中,當一個對象需要和另外的對象進行跨系統(tǒng)協(xié)作的時候,單例模式很有用。下面是一個單例模式在這種情況下使用的例子:
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 );
盡管單例模式有著合理的使用需求,但是通常當我們發(fā)現(xiàn)自己需要在javascript使用它的時候,這是一種信號,表明我們可能需要去重新評估自己的設(shè)計。
這通常表明系統(tǒng)中的模塊要么緊耦合要么邏輯過于分散在代碼庫的多個部分。單例模式更難測試,因為可能有多種多樣的問題出現(xiàn),例如隱藏的依賴關(guān)系,很難去創(chuàng)建多個實例,很難清理依賴關(guān)系,等等。
要想進一步了解關(guān)于單例的信息,可以讀讀 Miller Medeiros 推薦的這篇非常棒的關(guān)于單例模式以及單例模式各種各樣問題的文章,也可以看看這篇文章的評論,這些評論討論了單例模式是怎樣增加了模塊間的緊耦合。我很樂意去支持這些推薦,因為這兩篇文章提出了很多關(guān)于單例模式重要的觀點,而這些觀點是很值得重視的。