[Note]Creational Singleton Pattern

什麼是Singleton Pattern(單例模式)呢?

定義:一個Class在系統中只會存在一個實例(instance),整個系統中只提供1個可使用的instance,以確保唯一性,並節省系統資源。

簡單例子

注: 以下程式單純用於解釋,並不能實際執行

class的建構子(constructor)對外隱藏,外部無法通過constructor進行實例化,但會提供一個靜態(static)方法,以獲取這個class的唯一實例

TaskManager

//Constructor 對外隱藏,並提供一個靜態Static 讓外部只透過這個存取
class TaskManager{
private:
	TaskManager();
	void displayProcess();
	void displayServices();
	static TaskManager* taskManager = nullptr; //保存唯一Instance
public:
	//外部只能透過靜態Static 方法存取 並new一個TaskManger的Instance 如果是null的
	//此方法為工廠
	static TaskManager* getInstance(){
        if(taskManager == nullptr){
            taskManager = new TaskManager();
        }
        return TaskManager;
    }
}

LoadBalance

//LoadBalance 🌰
//用於計算服務器負載,所以必須使用單例
class LoadBalance{
public:
	static LoadBalance* getInstance(){
        if(loadBalance == nullptr){
            loadBalance = new LoadBalance();
        }
        return LoadBalance;
    }
private:
	static LoadBalance* loadBalance;
	LinkedList* serverList = nullptr;

	LoadBalance();
	void addServer(std::string server){
        serverList->add(server);//add a server
    }

	std::string getServer(){
        int i = random()% serverList->length(); //random get server
        return serverList->get(i);
    }

}

int main(){
    LoadBalance *balancer1,*balancer2,*balancer3,*balancer4;
    balancer1 = LoadBalance::getInstance(); //獲取LoadBalance,這裡會先new 再return
    balancer2 = LoadBalance::getInstance(); //獲取LoadBalance
    balancer3 = LoadBalance::getInstance(); //獲取LoadBalance
    balancer4 = LoadBalance::getInstance(); //獲取LoadBalance

    //以上4個balancer 都應該為同一記憶體
    if(balancer1 == balancer2 && balancer2 == balancer3 && balancer3 == balancer4)
    printf("Balancer is nnique");

    //add server
    balancer1->addServer("server1");
    balancer1->addServer("server2");
    balancer1->addServer("server3");
    balancer1->addServer("server4");

    for(int i = 0;i<10;i++){
        printf("server %d",balancer1->getServer());
    }
    return;
}

Singleton的問題

因為外部不能實例化,哪應該什麼時候實例化它呢?
從上面例子可以觀察到,第一次使用到getInstance()的時候,會先判斷是否為空,是的話會先實例化並保存pointer,然後再返回Instance。

如果在多線程(Multi-threading)下,同時call getInstance() 會這樣呢?
沒錯!就會導致違反了Sington的目的,導致系統錯誤的嚴重後果。
假如TA和TB 2個Thead 同時call getInstance(),此時這個instance是為null,所以他們都會同時滿足if(instance == null),並進行new instance,導致系統記憶體中會有2個Instance.

哪要如何解決呢?
以下提供了2個方法來解決

  • 餓漢式
    • 在定義class的時候就先把static variable先給實例化,因此,在加載時候就已經被實例化了。 可以想象成不管要不要用(吃),先實例化了再說(把食物買了再說) (就怕自己會餓死 xD
    • 問題: 可能會浪費資源,因為有可能會出現不使用的情況出現。
  • 懶漢式
    • 要使用的時候,先檢測是否存在,再實例化(就是上面例子一樣)
    • 問題 1: 在多線程的情況下,會變得不安全。為了確保線程安全,就必須要用到Mutex進行保護(會增加開銷)
    • 問題 2: 雖然有加上鎖,但是有可能線程會一起進入if 判斷,等一個thread解鎖了,另外一個還是會重新new 新的一個。因此,要在裡面加多一個if來判斷是否為null。這個稱為double-Check Locking

能克服餓漢式 以及 懶漢式的方法:
在Class內部定義static class來進行實現
因為static Singleton* instance 並不是Class Singleton的成員(Member),所以在加載時候,不會被實例化,只有呼叫了getInstance 的時候才會被實例化。

class Singleton {
    private:
		Singleton() {}
        static class HolderClass {
                private:
                    static Singleton* instance = new Singleton();
        }
        public:
            static Singleton getInstance() {
                return HolderClass::instance
            }
        }
}   

優點

  • 為系統提供了唯一的Instance
  • 因只有一個Instance,能節省系統資源
  • 在Singleton 模式中,除了唯一的,也可以提供指定數量的Instance,以解決分享過多造成的性能問題

缺點

  • 難以擴展,因為Singleton 不是由 Abstract Class實現

違背原則

  • 有點違反單一職責原則(Single-responsibility Principe)
    • 因又是工廠的角色(new instance),又是Product的角色(返回Static 獲取實例)

參考資料:
史上最全设计模式导学目录(完整版)