Java設計模式之從[反恐精英控制檯]分析單例(Singleton)模式

Froser發表於2014-04-06

  所謂單例模式(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 內部靜態類

  當一個類只期望有一個例項,且客戶可以從一個眾所周知的地方訪問它時,就可以考慮單例模式了。

相關文章