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

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

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

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

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


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

public class Singleton2
{

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

private static Singleton2 instance = null;


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

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


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

var Singleton =
{
attribute1: true;
attribute2: 10

method1: function()
{
},

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

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訪問單體中的其他方法,這是訪問單體中其他成員或方法的最簡便的方法。但這樣做有一點風險,因為this并不一定指向GiantCorp.DataParser例如,如果把某個方法用作事件監聽器,那么其中的this指向的是window對象,因此大多數JavaScript庫都會為事件關聯進行作用域校正,例如在這里使用GiantCorp.DataParser比使用this更為安全。
4.2.2使用閉包
在單例對象中創建私有成員的第二種方法是借助閉包。因為單例只會被實例化一次,所以不必擔心自己在構造函數中聲明了多少成員。每個方法和屬性都只會被創建一次,所以可以把它們聲明在構造函數內部(因此也就位于同一個閉包中)。
使用閉包創建擁有私有成員的單例類的實例如下:

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

function privateMethod1()
{

}


function privateMethod2()
{

}

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

publicMethod1: function()
{

},

publicMethod2: function()
{

}
};
})();
這種單例模式又稱為模塊模式,指的是它可以把一批相關方法和屬性組織為模塊并起到劃分命名空間的作用。
使用該種方式改造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;
}
};
})();
將私有成員放在閉包中可以確保其不會在單例對象之外被使用,因此開發人員可以自由的改變對象的實現細節,而不會殃及別人的代碼。還可以使用這種辦法對數據進行保護和封裝。
4.3 在JavaScript中實現“懶漢式”單例模式
在如上的代碼中,單例對象都是在腳本加載時被創建出來。對于資源密集型或配置開銷甚大的單例,更合理的是使用3.2小節中提到的“懶漢式”單例實現。這種實現方式的特別之處在于,對它們的訪問必須借助于一個靜態方法,例如調用單例類的getInstance()方法獲得對象實例。
參考實現代碼如下:

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

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

}


return
{

getInstance: function()
{

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

SimpleXhrFactory.createXhrObject();

</script>

</head>

<body>

</body>

</html>

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