---
# System prepended metadata

title: 6. Singleton
tags: [DesignPatterns]

---

# 6. Singleton

###### tags: `DesignPatterns`

## 關於 Singleton 本篇將討論以下幾個問題

> ### 1. 關於 Singleton
> ### 2. UML
> ### 3. 將 UML 轉為程式碼
> ### 4. 情境

---
## 測試環境：
>OS：Windows 10
>IDE：Visual Studio 2019
   
---
## 1. 關於 Singleton
> Ensure a class only has one instance, and provide a global point of access to it. 
> 
> by [Gang of Four](https://en.wikipedia.org/wiki/Design_Patterns)

- 確保 Singleton class 只有一個實體，並提供全局對這個實體的訪問點

Singleton(單例)屬於創建型(Creational Patterns)，當遇到不同呼叫端需要取得**相同實體**時可以使用 singleton，不過 singleton 由於許多缺點，所以並不建議用於解決複雜情形的簡化，而是在取得相同實體時才採用。

優點：
- 確保每次取得都是同一個實體，只有實體不存在時才需要初始化

缺點：
- 需考慮到 thread safe 問題
- 對於抽象、繼承、多型支援度較差
- 單元測試較麻煩

---
## 2. UML
![](https://i.imgur.com/AyemnJK.jpg)

Class：

- Singleton：作為單例的實體

---
## 3. 將 UML 轉為程式碼
此範例的寫法不是 thread safe，關於 Singleton 的 thread safe 寫法可參考 [C# in Depth](https://csharpindepth.com/Articles/Singleton)

建立`Singleton`實體，`_instance` 不存在才會 new 出新的實體
```C#
/// <summary>
/// Singleton 實體
/// </summary>
public class Singleton
{
    private static Singleton _instance;

    // 建構子通常為 protected or private，避免由外部建立實體
    protected Singleton()
    {
    }

    public static Singleton Instance()
    {
        // 使用延遲載入，此方式並非 thread safe
        return _instance ??= new Singleton();
    }
}
```

1. 分別呼叫`Singleton.Instance()`取得實體
2. 比較兩次取得的實體是否相同

```C#
static void Main(string[] args)
{
    Default.Singleton singletonA = Default.Singleton.Instance();
    Default.Singleton singletonB = Default.Singleton.Instance();
    
    if (singletonA == singletonB)
    {
        Console.WriteLine("\nSame instance");
    }

    Console.ReadLine();
}
```
執行結果
```Console
Same instance
```

---
## 4. 情境
我們接到了一個線上商城取得倉庫庫存量資訊的需求

- 倉庫庫存資訊不提供外部修改
- 倉庫庫存資訊資料量很大，很佔記憶體空間

建立`InStockModel`實體，`_instance`不存在才會 new 出新的實體
```C#
/// <summary>
/// Singleton 實體
/// </summary>
public class InStockModel
{
    private static InStockModel _instance;

    protected InStockModel()
    {
        Console.WriteLine($"Get in stock data by API.");
    }

    public static InStockModel Instance()
    {
        // 使用延遲載入，此方式並非 thread safe
        return _instance ??= new InStockModel();
    }
}
```

1. 分別呼叫`InStockModel.Instance()`取得實體
2. 確認是否只有在第一次呼叫時進入建構子
3. 比較兩次取得的實體是否相同

```C#
static void Main(string[] args)
{
    Situation.InStockModel inStockModelA = Situation.InStockModel.Instance();
    Situation.InStockModel inStockModelB = Situation.InStockModel.Instance();
    
    if (inStockModelA == inStockModelB)
    {
        Console.WriteLine("\nSame instance");
    }
    
    Console.ReadLine();
}
```
執行結果
```Console
Get in stock data by API.

Same instance
```

---
## 完整程式碼
GitHub：[Creational_05_Singleton](https://github.com/darionnnnnn/blog/tree/master/Blog/Creational_05_Singleton)
   
---
## 總結
### Singleton 使用上簡單易上手，常被直接做為掌控度較高的全域變數使用，但會使使用端與 Singleton 類別之間耦合度提高，且儘管程式內部沒有以多執行緒方式運行，不過單元測試執行時是以多執行緒進行，若沒處理 thread safe 則依然可能會遇到單元測試時過時不過的問題。

---
## 參考資料
1. [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns)
2. [大話設計模式](https://www.tenlong.com.tw/products/9789866761799)
2. [dofactory](https://www.dofactory.com/)
3. [Refactoring.Guru](https://refactoring.guru/)
4. [C# in Depth](https://csharpindepth.com/Articles/Singleton)

---

## 新手上路，若有錯誤還請告知，謝謝