iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 22
0
自我挑戰組

再戰軟體工程系列 第 21

『How Old Are You:怎麼老是你』 -- 從Singleton談設計模式

前面講了幾篇設計模式的好處,事實上,大自系統架構,小至單一個物件的創見,都可以看見設計模式的影子。今天我們從最簡單不過的『創建單例物件』(Singleton)來看,設計模式怎麼解決我們的問題。

講到Singleton,直接翻白眼並且想轉台的朋友肯定不少:『Singleton那啥東西,我大一就會了,我一分鐘就寫給你看!』

Eager Initialization

public class EagerInitialization {
    private EagerInitialization() {}
    private static EagerInitialization eager = new EagerInitialization();
    public static EagerInitialization getInstance(){
        return eager;
    }
}

很簡單啊!這有什麼好專文寫的?

是啊,設計模式就是這麼簡單,短短幾行,就利用單一物件解決記憶體的問題。事實上,這就叫做Eager Initialization的Singleton方法。而坊間與Eager Initialization相似的創造方法,還有下列的Static Block方法。兩者都是在static階段就把物件創造出來用,但是後者解決了前者半不到的事:提供『try-catch』的空間:

Static Block

public class StaticBlock {

    private static StaticBlock instance;

    static {
        try{
            instance = new StaticBlock();
        }catch(Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }

    public static StaticBlock getInstance(){
        return instance;
    }

}

如此一來,即便在創建Singleton的過程中有什麼Exception發生,我們也不怕了。

然而,上述的兩種方法都是在static時期就創建好物件,而在應用上,如果在我們的程式裡,這樣的物件有非常多,卻不是大家都會很快被用到時,提前創建好好像又有點浪費空間。況且,程式啟動時有很多事情要忙,多負擔這些很久一後才會用到的東西其實不太好。那怎麼辦?

那就要用時再創吧!

Lazy Initialization

public class LazyInitialization {
    private static LazyInitialization instance;

    public static LazyInitialization getInstance(){
        if (null == instance){
            instance = new LazyInitialization();
        }
        return instance;
    }
}

嗯嗯,這樣一來就不怕太早創太多物件來浪費記憶體了。

然而,聰明的你一定發現了,因為不是在static階段創建,所以也許會有多執行續同時需要用到的情形,這麼一來,thread-safe就變成必要的了,而上述創建方法卻無法保證線程安全性。那麼就改造它吧!讓創建過程變得安全一點:我們還是一樣等要使用時才創建,但是這次加了synchronized block來保障線程安全性。

Thread Safe Singleton

public class ThreadSafeSingleton {
    private static ThreadSafeSingleton instance;

    public static synchronized ThreadSafeSingleton getInstance(){

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

    }
}

但是,我們都知道進出Synchronized Block的系統成本是很高的,為了只創一次的物件,每次讀都要鎖單一線程,未免有點因小失大了,而我們又不希望因為不鎖上而導致兩個線程同時創建物件而導致錯誤,怎麼辦?很簡單,只要檢查兩次就好啦!第一次先在synchronized block外面檢查物件是否存在,萬一不存在才進入synchronized block。進來以後還要再檢查一次,避免在剛剛那一瞬間已經又被捷足先登了。這就是坊間常用的Double Check Thread Safe設計模式。

Double Check Thread Safe Singleton

public class DoubleCheckThreadSafeSingleton {
    private DoubleCheckThreadSafeSingleton instance;

    public DoubleCheckThreadSafeSingleton getInstance(){
        if (null == instance){
            synchronized (DoubleCheckThreadSafeSingleton.class) {
                if (null == instance){
                    instance = new DoubleCheckThreadSafeSingleton();
                }
            }
        }
        return instance;
    }
}

還有其他...

Double check thread safe應該要算是最常用的Singleton創建設計模式了。在上述的方法之外,還有一些創建Singleton的設計模式,像是Java 5以前常用的BillPugh,以及適用於分散式系統的SerializedSingleton等等,這裡就不一一詳述了,但是我的附檔的程式裡還是有的,可以下載來嘗試看看!

小結

看,光是一個Singleton就有這麼多種實作,各有其巧妙之處與適用場警。而總的來說,設計模式到處可見,在各種環境與需求下,我們可以先查查看有沒有前人已經發明過的設計模式可以套用,這樣一來不只省下嘗試與錯誤的時間,也可以提升自己程式的品質與可靠度。

本文範例程式:https://github.com/bearhsu2/ithelp


上一篇
『就決定是你了』 -- 談PO的適合人選
下一篇
『雞尾酒式的scrum』 -- 談台灣最常見的 WaterScrum
系列文
再戰軟體工程30

1 則留言

1
微中子
iT邦新手 4 級 ‧ 2018-01-05 21:59:00

講到單例模式,就會想到依賴注入

Kuma iT邦新手 5 級‧ 2018-01-05 23:13:12 檢舉

而講到依賴注入,就想到Spring框架 :)

Kuma iT邦新手 5 級‧ 2018-01-05 23:13:13 檢舉

而講到依賴注入,就想到Spring框架 :)

我要留言

立即登入留言