寫在前面
singleton模式是被熟知的原因是因為它限制了類的執行個體化次數只能一次。從經典意義上來說,singleton模式在該執行個體不存在的情況下,可以通過一個方法建立一個類來實現建立類的新執行個體;如果執行個體已經存在,它會簡單返回該對象的引用。
singleton不同於靜態類(或對象),因為我們可以延遲它們的初始化,這通常是因為它們需要一些資訊,而這些資訊在初始化期間可能無法獲得、對於沒有察覺到之前的引用的代碼,它們不會提供方便檢索的方法。這是因為它既不是對象,也不是由一個singleton返回的“類”;它是一個結構。
思考一下閉包變數為何實際上並不是閉包,而提供閉包的函數範圍是閉包。在Javascript中,singleton充當共用資源命名空間,從全域命名空間中隔離出代碼實現,從而為函數提供單一訪問點。 來個例子
var mySingleton=(function(){ //執行個體保持了Singleton的一個引用 var instance; function init(){ //Singleton //私人方法和變數 function privateMethod(){ console.log("I am private"); } var privateVariable="I am 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{ //擷取singleton的執行個體,如果存在則返回,不存在就建立新執行個體 getInstance:function(){ if(!instance){ instance=init(); } return instance; } }})();var myBadSingleton=(function(){ //執行個體儲存了singleton的一個引用 var instance; function init(){ //Singleton 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()); //truevar badSingleA=myBadSingleton.getInstance();var badSingleB=myBadSingleton.getInstance();console.log(badSingleA.getRandomNumber()!==badSingleB.getRandomNumber()); //true
分析
好了,寫了這麼多代碼,我們先明確一個問題——是什麼使Singleton成為執行個體的全域訪問入口(通常通過MySingleton.getInstance())?
因為我們沒有(至少在靜態語言中)直接調用新的MySingleton()。然而,這在Javascript中是可能的。 適用的情境
1、當類只能有一個執行個體而且客戶可以從一個眾所周知的訪問點訪問它時。
2、該唯一的執行個體應該是通過子類化可擴充的,並且客戶應該無需更改代碼就能使用一個擴充的執行個體時。 順延強制
我們看下面代碼:
mySingleton.getInstance=function(){ if(this._instance==null){ if(isFoo()){ this._instance=new FooSingleton(); }else{ this._instance=new BasicSingleton(); } } return this._instance;};
在這裡,getInstance變得有點像Factory(工廠)方法,當訪問它時,我們不需要更新代碼中的每個訪問點。FooSingleton(上面)將是一個BasicSingleton的子類,並將實現相同的介面。
那麼問題來了,為什麼要順延強制Singleton呢。
在C++中,Singleton負責隔絕動態初始化順序的不可預知性,將控制權歸還給程式員。
值得注意的是類的靜態執行個體(對象)和Singleton之間的區別:當Singleton可以作為一個靜態執行個體實現時,它也可以延遲構建,直到需要使用靜態執行個體時,無需使用資源或記憶體。
如果我們有一個可以直接被初始化的靜態對象,需要確保執行代碼的順序總是相同的(例如:在初始化期間objCar需要objWheel的情況),當我們有大量的源檔案時,它並不能伸縮。
Singleton和靜態對象都是有用的,但是我們不應當以同樣的方式過度使用它們,也不應該過度地使用其他模式。 最佳實務
在實踐中,當在系統中確實需要一個對象來協調其他對象時,Singleton模式是很有用的,在這裡,大家可以看到在這個上下文中模式的使用:
var SingletonTester=(function(){ //options:包含Singleton所需配置資訊的對象 //@eg: var options={name:"test",pointX:5}; function Singleton(options){ //如果未提供options則設定為空白對象 options=options || {}; //未Singleton設定一些屬性 this.name="SingletonTester"; this.pointX=options.pointX||6; this.pointY=options.pointY||10; } //執行個體持有人 var instance; //靜態變數和方法的類比 var _static={ name:"SingletonTester", //擷取執行個體的方法,返回Singleton對象的Singleton執行個體 getInstance:function(options){ if(instance===undefined){ instance=new Singleton(options); } return instance; } }; return _static;})(); var singletonTest=SingletonTester.getInstance({ pointX:5});//記錄pointX的輸出以便驗證//輸出:5console.log(singletonTest.pointX);
總結
Singleton很有使用價值,通常當發現在Javascript中需要它的時候,則表示我們可能需要重新評估我們的設計。Singleton的存在往往表面系統中的模組要麼是系統緊密耦合,要麼是其邏輯國語分散在程式碼程式庫的多個部分。由於一系列的問題:從隱藏的依賴到建立多個執行個體的難度、底層依賴的難度等等,Singleton的測試會更加困難。