# 單例模式
單例模式之所以這么叫,是因為它限制一個類只能有一個實例化對象。經典的實現方式是,創建一個類,這個類包含一個方法,這個方法在沒有對象存在的情況下,將會創建一個新的實例對象。如果對象存在,這個方法只是返回這個對象的引用。
單例和靜態類不同,因為我們可以退出單例的初始化時間。通常這樣做是因為,在初始化的時候需要一些額外的信息,而這些信息在聲明的時候無法得知。對于并不知曉對單例模式引用的代碼來講,單例模式沒有為它們提供一種方式可以簡單的獲取單例模式。這是因為,單例模式既不返回對象也不返回類,它只返回一種結構。可以類比閉包中的變量不是閉包-提供閉包的函數域是閉包(繞進去了)。
在JavaScript語言中, 單例服務作為一個從全局空間的代碼實現中隔離出來共享的資源空間是為了提供一個單獨的函數訪問指針。
我們能像這樣實現一個單例:
~~~
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 {
// 如果存在獲取此單例實例,如果不存在創建一個單例實例
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 {
// 總是創建一個新的實例
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
~~~
創建一個全局訪問的單例實例 (通常通過 MySingleton.getInstance()) 因為我們不能(至少在靜態語言中) 直接調用 new MySingleton() 創建實例. 這在JavaScript語言中是不可能的。
在四人幫(GoF)的書里面,單例模式的應用描述如下:
* 每個類只有一個實例,這個實例必須通過一個廣為人知的接口,來被客戶訪問。
* 子類如果要擴展這個唯一的實例,客戶可以不用修改代碼就能使用這個擴展后的實例。
關于第二點,可以參考如下的實例,我們需要這樣編碼:
~~~
mySingleton.getInstance = function(){
if ( this._instance == null ) {
if ( isFoo() ) {
this._instance = new FooSingleton();
} else {
this._instance = new BasicSingleton();
}
}
return this._instance;
};
~~~
在這里,getInstance 有點類似于工廠方法,我們不需要去更新每個訪問單例的代碼。FooSingleton可以是BasicSinglton的子類,并且實現了相同的接口。
為什么對于單例模式來講,延遲執行執行這么重要?
在c++代碼中,單例模式將不可預知的動態初始化順序問題隔離掉,將控制權返回給程序員。
區分類的靜態實例和單例模式很重要:盡管單例模式可以被實現成一個靜態實例,但是單例可以懶構造,在真正用到之前,單例模式不需要分配資源或者內存。
如果我們有個靜態對象可以被直接初始化,我們需要保證代碼總是以同樣的順序執行(例如 汽車需要輪胎先初始化)當你有很多源文件的時候,這種方式沒有可擴展性。
單例模式和靜態對象都很有用,但是不能濫用-同樣的我們也不能濫用其它模式。
在實踐中,當一個對象需要和另外的對象進行跨系統協作的時候,單例模式很有用。下面是一個單例模式在這種情況下使用的例子:
~~~
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 );
~~~
盡管單例模式有著合理的使用需求,但是通常當我們發現自己需要在javascript使用它的時候,這是一種信號,表明我們可能需要去重新評估自己的設計。
這通常表明系統中的模塊要么緊耦合要么邏輯過于分散在代碼庫的多個部分。單例模式更難測試,因為可能有多種多樣的問題出現,例如隱藏的依賴關系,很難去創建多個實例,很難清理依賴關系,等等。
要想進一步了解關于單例的信息,可以讀讀 Miller Medeiros 推薦的這篇非常棒的關于單例模式以及單例模式各種各樣問題的[文章](http://www.ibm.com/developerworks/webservices/library/co-single/index.html),也可以看看這篇文章的評論,這些評論討論了單例模式是怎樣增加了模塊間的緊耦合。我很樂意去支持這些推薦,因為這兩篇文章提出了很多關于單例模式重要的觀點,而這些觀點是很值得重視的。
- 前言
- 簡介
- 什么是設計模式?
- 設計模式的結構
- 編寫設計模式
- 反模式
- 設計模式的分類
- 設計模式分類概覽表
- JavaScript 設計模式
- 構造器模式
- 模塊化模式
- 暴露模塊模式
- 單例模式
- 觀察者模式
- 中介者模式
- 原型模式
- 命令模式
- 外觀模式
- 工廠模式
- Mixin 模式
- 裝飾模式
- 亨元(Flyweight)模式
- JavaScript MV* 模式
- MVC 模式
- MVP 模式
- MVVM 模式
- 最新的模塊化 JavaScript 設計模式
- AMD
- CommonJS
- ES Harmony
- JQuery 中的設計模式
- 組合模式
- 適配器模式
- 外觀模式
- 觀察者模式
- 迭代器模式
- 惰性初始模式
- 代理模式
- 建造者模式
- jQuery 插件的設計模式
- JavaScript 命名空間模式
- 總結
- 參考