iT邦幫忙

2022 iThome 鐵人賽

DAY 16
0
自我挑戰組

設計模式探索系列 第 16

[Day 16] 單例模式 (2)

  • 分享至 

  • xImage
  •  

以巧克力工廠為例

前面講了這麼多,還沒有實例登場。
這次,書中提到的範例是巧克力工廠─ 工廠的運作依賴著對巧克力鍋爐狀態的偵測,依據不同的狀態執行不同的動作,避免災難發生!它的運作方式如下:

class ChocolateBoiler
{
    private:
        bool empty;
        bool boiled;
    public:
        ChocolateBoiler()
        {
            empty = true;
            boiled = false;
        }

        void fill()
        {
            if(isEmpty())
            {
                empty = false;
                boiled = false;
                cout<<"fill!"<<endl;
            }
        }

        bool isEmpty()
        {
            return empty;
        }

        bool isBoiled()
        {
            return boiled;
        }

};

而因為只有一個鍋爐,我們必須避免讓兩個ChocolateBoiler實例被創造出來,因此加入前面經典單例模式的寫法:

class ChocolateBoiler
{
    private:
        static ChocolateBoiler *uniqueInstance;
        ChocolateBoiler()
        {
            empty = true;
            boiled = false;
        };
        bool empty;
        bool boiled;
    public:
        static ChocolateBoiler *getInstance()
        {
            if(!uniqueInstance)
            {
                uniqueInstance = new ChocolateBoiler();
            }
            return uniqueInstance;
        }
        void fill()
        {
            if(isEmpty())
            {
                empty = false;
                boiled = false;
                cout<<"fill!"<<endl;
            }
        }

        bool isEmpty()
        {
            return empty;
        }

        bool isBoiled()
        {
            return boiled;
        }

};

前面有提到這個寫法可能有潛在的問題,究竟為何?

多執行序下的問題

當現在有兩個執行序時,分別執行以下這段程式:

ChocolateBoiler * boiler = ChocolateBoiler::getInstance();
boiler->fill();

是否有可能有兩個實例同時被產生的情形呢?
例如在以下的情況下交錯...
https://ithelp.ithome.com.tw/upload/images/20221002/201400968KFkpIloAP.png

那要如何解決?

處理多執行序

有幾種方式可以處理這種情形:

  1. getInstance()改為同步方法。例如以下:
class Singleton
{
    private:
        static Singleton *uniqueInstance;
        Singleton(){};
        static mutex uniqueInstance_mtx;
        
    public:
        static Singleton *getInstance()
        {
            lock_guard<mutex> lock(uniqueInstance_mtx);
            if(!uniqueInstance)
            {
                uniqueInstance = new Singleton();
            }
            return uniqueInstance;
        }
};
  • 變成同步後,就可以確保不同執行序不會同時進入這個方法,但缺點是其實我們只需要避免第一次─實例還沒被創建之時的情形,之後的同步就完全沒有需要了,反而會造成累贅;不過,若getInstance()不常被使用,或不會太嚴重影響程式效能,就可以採用此方法。

2.使用 急性(eager) 而非 惰性(lazy) 創建實例。例如以下:

class Singleton
{
    private:
        static Singleton *uniqueInstance;
        Singleton(){};
        
    public:
        static Singleton *getInstance()
        {
            return uniqueInstance;
        }
};

Singleton * Singleton::uniqueInstance = new Singleton();
  • 但這就代表了不能等到真正有運用後才去創建實例,沒有惰性的優點。

3.使用 "雙重檢查鎖(double checked locking)" ,在getInstance()中減少同步化。例如以下:

class Singleton
{
    private:
        volatile static Singleton *uniqueInstance;
        Singleton(){};
        static mutex uniqueInstance_mtx;
        
    public:
        volatile static Singleton *getInstance()
        {            
            if(!uniqueInstance)
            {
                lock_guard<mutex> lock(uniqueInstance_mtx);
                if(!uniqueInstance)
                {   
                    uniqueInstance = new Singleton();
                }
            }
            return uniqueInstance;
        }
};
  • 這就降低了第一種方法帶來的效能負擔。

除此之外,還有java可直接使用enum來達到singleton的效果,且不會有執行序等等的副作用,可參考這邊的實作

結語

結束了輕鬆的這一回合,終於來到鐵人賽的一半!目前已經涵蓋了六章,歷經六個原則七個模式定義,不妨回頭整理複習一下這些模式與他們的架構圖,以及背後那些原則們,再繼續看下去各式各樣的模式們。


上一篇
[Day 15] 單例模式 (1)
下一篇
[Day 17] 命令模式 (1)
系列文
設計模式探索30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言