Java設計模式之從[反恐精英控制檯]分析單例(Singleton)模式
所謂單例模式(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 內部靜態類
當一個類只期望有一個例項,且客戶可以從一個眾所周知的地方訪問它時,就可以考慮單例模式了。