所謂單例模式(Singleton),就是保證一個類僅有一個實例,並提供一個訪問它的全局訪問點的模式。
玩過反恐精英的同學應該都知道,遊戲當中是有個控制檯的,我們可以通過按`鍵(波浪線鍵)調出這個控制檯。控制檯的目的是爲了方便開發人員調試,當然我們也可以在裏面來修改一些遊戲參數,如輸入SV_GRAVITY 100可以把重力調整到100,那麼我們跳躍的高度就是原來的8倍了。
由於控制檯的遊戲的全局通用的,因此我們希望這個控制檯類僅有一個實例。當我們訪問它的時候,如果它沒有實例化,則實例化之,如果它實例化了我們則返回它實例化的對象。這便是單例模式。
那麼,以Java爲例,我們應該在何時將類的對象實例化呢?是在第一次加載類的時候?第一次需要返回實例的時候?因爲對象的實例化時間順序的差異,我們可以寫出幾種單例模式的實現方法,本篇文字以懶漢、餓漢、嵌套類(內部靜態類)三種方法爲例。代碼如下:
class Log {
public void print(String str){
System.out.println(str + " - From 懶漢" );
}
private static Log logInstance;
private Log(){}
public static synchronized Log getInstance(){
if (logInstance == null){
logInstance = new Log();
}
return logInstance;
}
}
class Log2{
private static Log2 logInstance = new Log2();
private Log2(){}
public void print(String str){
System.out.println(str + " - From 餓漢" );
}
public static Log2 getInstance(){
return logInstance;
}
}
class Log3{
private static class LogHolder{
private static final Log3 logInstance = new Log3();
}
private Log3(){}
public static Log3 getInstance(){
return LogHolder.logInstance;
}
public void print(String str){
System.out.println(str + " - From 內部靜態類" );
}
}
class Singleton
{
public static void main(String[] args) {
Log log = Log.getInstance();
log.print("Hello world");
Log2 log2 = Log2.getInstance();
log2.print("Hello world");
Log3 log3 = Log3.getInstance();
log3.print("Hello world");
}
}
在上面的例子中,Log、Log2和Log3分別用懶漢、餓漢、內部靜態類實現了單例模式,它們能夠調用print方法,輸出我們傳入的字符串。下面簡要來分析一下這3個類之間的差異。
Log類可以調用getInstance來返回一個Log實例,如果這個實例沒有被創建,則進行創建,否則直接返回實例,這個就是懶漢模式(需要的時候才進行判斷)。由於判斷是否存在和創建存在時間差,因此我加上了synchronized關鍵字保證它不會在多線程中遭到爭搶。這樣寫的代價是,系統會在同步鎖上有很大的開銷,假設很多地方需要使用到getInstance,那麼時間開銷會很大。
Log2類,將實例化寫在了一個static語句中,那麼,只要這個類被加載(這個類第一次被使用)時,就會創建logInstance對象。假設這個對象十分龐大,那麼我們在加載這個類的時候會花很多時間。另外,假設new Log2()執行失敗,我們將永遠無法得到Log2的實例。只要這個類被加載,則對象被創建,這個就是餓漢。
Log3類包含一個嵌套類(內部靜態類),它是Log2類的升級版。我們可以看到,logInstance的實例化被寫到了一個嵌套類中,那麼這個嵌套類被加載的時間也就是調用Log3.getInstance的時間,也就是說,只有我們調用了Log3.getInstance,這個對象纔會被創建。
以上程序的運行結果爲:
Hello world - From 懶漢
Hello world - From 餓漢
Hello world - From 內部靜態類
當一個類只期望有一個實例,且客戶可以從一個衆所周知的地方訪問它時,就可以考慮單例模式了。