# Singleton Pattern 單例模式

**URL:** https://vip.studycamp.tw/t/singleton-pattern-%E5%96%AE%E4%BE%8B%E6%A8%A1%E5%BC%8F/6486
**Category:** Design Patterns
**Created:** [2023年九月22日 07:13 UTC](https://vip.studycamp.tw/t/singleton-pattern-%E5%96%AE%E4%BE%8B%E6%A8%A1%E5%BC%8F/6486 "2023-09-22T07:13:23Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![sky](https://vip.studycamp.tw/user_avatar/vip.studycamp.tw/sky/32/8098_2.png) [@sky](https://vip.studycamp.tw/u/sky)
#### Post date: [2023年九月22日 07:13 UTC](https://vip.studycamp.tw/t/singleton-pattern-%E5%96%AE%E4%BE%8B%E6%A8%A1%E5%BC%8F/6486/1 "2023-09-22T07:13:24Z")

</div>

## 什麼是 Singleton Pattern 單例模式

> Singleton 確保一個類別只有一個實例，並提供對該實例的全域存取。
> 
> 該模式並沒有規定如何處理，這是我們可以發揮創意的地方。

在實作上，你必須 **自己控制實例化（instantiation）方式** 。

由於是全域存取唯一的實例，相對應的處理必須 **序列化（Serialization）** ，保證不同客戶端來的需求，都能依序處理。

1. Ensure that the class has only a single instance.

2. Provide easy global access to this instance.

3. Control how it is instantiated.

4. Any critical region must be entered serially.

GoF 四人幫對 Singleton Pattern 單例模式的描述為：

> It’s important for some classes to have exactly one instance. Although there can be many printers in a system, there should be only one printer spooler. There should be only one file system and one window manager…（即「列印多工緩衝處理器」服務）

* * *

## 何處使用

雖然很多人說不要使用 Singleton Pattern 單例模式，事實上還是有適用之處。例如（應該一看秒懂）：

- **Loggers**

- **Caching**

- **Thread Pools**

- **Database Connections**

- **Configuration Access**

> 我們的共學課程，提到的是 object factory，我反而不太了解和 Singleton 的關係。

單例經常與其他模式一起使用（應該看實例）：

1. Abstract Factory

2. Builder

3. Prototype

4. Facade

5. State

* * *

## 使用時機

### 何時使用

當你想要控制對 **共享資源** 的存取。

### 何時不應該使用

1. 謹慎使用，不要讓它退化到 **只剩全域存取的功能** ，這樣會使程式難以理解和維護。

2. **是否違反了 SRP（單一責任原則）** ？

* * *

## 設計考量

### 1. 載入時機

有兩種設計 Singleton Pattern 單例模式的思維：

#### Lazy Construction（延遲建構）

> 實例只在第一次要使用前創建。（也可以稱為 Lazy Loading，比較對仗）

如果不確定何時使用，也不想浪費資源，可以考慮這種方式。

#### Eager Loading（急切載入）

> 無論是否馬上用到，一開始就先準備好。

範例：射擊遊戲中的音效。你在遊戲開始時，就使用單例載入，但等到遊戲中真正發射時才存取播放。

**主要的考量在記憶體。**

### 2. Thread Safety（線程安全）

因為 Python 支援多執行緒，我們在設計 **多執行緒的單例** 時，會用鎖住關鍵區塊（lock the Critical Section）來妥善安排其 **序列化（Serialization）**

## Singleton Pattern Architecure 單例模式架構

單例模式可以透過以下三種方式達成：

- Base class

- Decorator

- Meta class（最合適）

不過在開始前，我們先看單例模式的基礎架構（與 Python 無關）。

### 通用型 Singleton Pattern

通常透過以下方式完成：

- 將類別的所有建構函式宣告為 private，防止它被其他物件實例化

- 提供傳回實例引用的靜態方法

實例通常儲存為私人靜態變數；該實例是在變數初始化時創建的，在首次呼叫靜態方法之前的某個時刻創建。

> 以下表格模仿 UML 說明

| Singleton | 說明 |
| --- | --- |
| - instance: Singleton | private static instance  
在類別層級而不是物件層級定義，所以只能有一個實例。 |
| | |
| - Singleton() | private constructor 防止外部呼叫實例化 |
| + getInstance(): Singleton | 透過呼叫 static getInstance 方法來取得實例  
這種方式是 Lazy Construction（延遲建構） |

```python
if (instance == null):
    instance = new Singleton()
return instance

```

> **隱藏細節**
>
> 示意圖中，有一個名為 Singleton 的類別，它具有以下元素：
> 
> 1. **instance** : 這是一個靜態（或類別級別）成員變數，它保存了 Singleton 類的唯一實例。
> 
> 2. **constructor()**: 這是一個私有的建構函數，它用來防止從外部創建額外的實例。只有 Singleton 類自己的內部方法可以調用它。
> 
> 3. **getInstance()**: 這是一個靜態方法，它提供了對 Singleton 實例的全局訪問點。如果 Singleton 實例已經存在，則返回該實例；否則，它會創建一個新的實例並返回。
> 
> Singleton 設計模式確保了一個類別只能有一個實例存在，並提供了一個方便的方式來訪問該實例。這對於需要共享單一資源或控制全局設置的情況非常有用。通常，Singleton 模式還可以實現一些額外的功能，如延遲實例化（只有在需要時才創建實例）或執行緒安全（確保多執行緒環境下也能正確運作）。不過，上面的示意圖只顯示了 Singleton 模式的基本結構。

## 方法一：改寫 ` __new__ `

> 本節介紹的方式，是改寫 allocator（即 ` __new__ `），但如果只實作 ` __new__ `，對於有 ` __init__ ` 的 class 會有問題，所以要一併改寫 ` __init__ `。
> 
> 簡單的說，就是建立一個 \_instance 參數。如果沒創建過，就創建並記錄進該參數；如果創建過，就回傳該參數。

```python
import random

class Database:
    ## initialized = False

    def __init__ (self):
        self.id = random.randint(1,101)
        print('Generated an id of ', self.id)
        print('Loading database from file')
        pass

    _instance = None

    def __new__ (cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Database, cls). __new__ (cls, *args, **kwargs)

        return cls._instance

```

```python
d1 = Database()
d2 = Database()

print("===============================")
print(d1.id, d2.id)
print(d1 == d2)

```

輸出：

```python
Generated an id of 82
Loading database from file
Generated an id of 10
Loading database from file
===============================
10 10
True

```

## 方法二：decorator 裝飾器

> 裝飾器的方法，很接近方法一。
> 
> 在 class singleton，建立一個 \_instances 參數（dict）。然後在 get\_instance 檢查，如果沒創建過，就創建並記錄進該參數；如果創建過，就回傳該參數。
> 
> 然後使用 decorator 裝飾器把 class Database 包起來。

```python
def singleton(class_):
    _instances = {}

    def get_instance(*args, **kwargs):
        if class_ not in _instances:
            _instances[class_] = class_(*args, **kwargs)
        return _instances[class_]

    return get_instance

@singleton
class Database:
    def __init__ (self):
        print('Loading database')

d1 = Database()
d2 = Database()
print("===============================")
print(d1 == d2)

```

輸出：

```python
Loading database ## 只列印一次
===============================
True

```

## 方法三：metaclass 元類別

> metaclass 是最適合的做法，透過 ` __call__ ` 這個 method 來控制是否創建。
> 
> 在 class singleton，建立一個 \_instances 參數（dict）。然後在 ` __call__ ` 檢查，如果沒創建過，就創建並記錄進該參數；如果創建過，就回傳該參數。

```python
class Singleton(type):
    """ Metaclass that creates a Singleton base type when called. """
    _instances = {}

    def __call__ (cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls). __call__ (*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=Singleton):
    def __init__ (self):
        print('Loading database')

d1 = Database()
d2 = Database()
print("===============================")
print(d1 == d2)

```

輸出：

```python
Loading database ## 只列印一次
===============================
True

```

## Monostate（Borg）

> `Monostate` 和 `Singleton` 在確保應用程式中，可以全域存取同樣的資料，但實現的方式不同。
> 
> `Monostate` 可以有許多實例，這些實例共享相同的屬性（狀態）、
> 
> `Singleton` 單例可以具有屬性，但主要特點是只有一個實例（同一個實例的屬性，當然相同）。

### 設計目的

- **Monostate** ：Monostate 確保在應用程式中的 **所有實例共享相同的狀態** 。每個實例都可以設置狀態，但所有實例都將讀取和修改相同的狀態變數。Monostate 主要用於共享狀態而不是單例實例。

- **Singleton** ：Singleton 確保在整個應用程式中只有一個實例可用。這種模式的主要目的是 **限制類別的實例數量** ，以確保全局資源或服務的獨一性。

### 狀態共享

- **Monostate** ：所有 Monostate 的實例都共享相同的狀態變數。當一個實例修改狀態時，所有其他實例都會受到影響，因為它們都操作同一個狀態變數。

- **Singleton** ：Singleton 的主要重點是確保只有一個實例存在，而不關心狀態的共享。單例可以具有狀態，但它的主要特點是只有一個實例。

### 實作方式

- **Monostate** ：實作 Monostate 通常在類中 **使用 靜態成員變數 來存儲狀態** 。這使得所有實例都共享相同的狀態。

- **Singleton** ：實作 Singleton 通常使用私有的建構函數、靜態成員變數和一個靜態方法（通常稱為 `getInstance`）來確保只有一個實例。Singleton 也可以擁有私有的狀態變數，但主要目的是確保實例數量。

```python
# all members are static

class CEO: ## Monostate 模式
    __shared_state = { ## CEO 的共享狀態，即 'name' 和 'age'。
        'name': 'Steve',
        'age': 55
    }

    def __init__ (self):
        ## 將實例的 __dict__ 設置為 __shared_state，這樣所有 CEO 的實例都共享相同的狀態。
        self. __dict__ = self.__shared_state

    def __str__ (self):
        return f'{self.name} is {self.age} years old'

class Monostate: ## 實現 Monostate 模式的基類
    _shared_state = {} ## 類別層級變數，用於存儲共享的狀態

    def __new__ (cls, *args, **kwargs): ## 改寫 ` __new__ `，以確保只有一個實例存在。
        obj = super(Monostate, cls). __new__ (cls, *args, **kwargs)
        obj. __dict__ = cls._shared_state
        return obj

class CFO(Monostate): ## 繼承 Monostate 並定義了自己的狀態
    def __init__ (self): ## 定義自己的狀態變數，包括 'name' 和 'money_managed'
        self.name = ''
        self.money_managed = 0

    def __str__ (self):
        return f'{self.name} manages ${self.money_managed}bn'

```

```python
ceo1 = CEO()
print(ceo1) ## Steve is 55 years old

ceo1.age = 66

ceo2 = CEO()
ceo2.age = 77
print(ceo1) ## Steve is 77 years old
print(ceo2) ## Steve is 77 years old

ceo2.name = 'Tim'

ceo3 = CEO()
print(ceo1, ceo2, ceo3) ## Tim is 77 years old Tim is 77 years old Tim is 77 years old

cfo1 = CFO()
cfo1.name = 'Sheryl'
cfo1.money_managed = 1

print(cfo1) ## Sheryl manages $1bn

cfo2 = CFO()
cfo2.name = 'Ruth'
cfo2.money_managed = 10
print(cfo1, cfo2, sep='\n') ## Ruth manages $10bn \n Ruth manages $10bn

```

## 比較簡潔的例子

```python
class Monostate:
    _shared_state = {} # 這是共享的狀態變數

    def __init__ (self):
        self. __dict__ = self._shared_state # 將 __dict__ 設置為共享狀態

    def set_data(self, key, value):
        self._shared_state[key] = value

    def get_data(self, key):
        return self._shared_state.get(key)

# 創建兩個 Monostate 實例
instance1 = Monostate()
instance2 = Monostate()

# 設置狀態
instance1.set_data("name", "Alice")

# 檢查狀態
print("Instance 1 Name:", instance1.get_data("name")) # 這會輸出 "Instance 1 Name: Alice"
print("Instance 2 Name:", instance2.get_data("name")) # 這也會輸出 "Instance 2 Name: Alice"
print(instance1 == instance2)

```

輸出：

```python
Instance 1 Name: Alice
Instance 2 Name: Alice
False

```

## Singleton Testability

> 因為 Singleton 只有一個實例，所以測試時就是那個實例。對於線上資料庫，這樣做有風險。
> 
> 解決方式很單純，就是多建一個 Dummy Database 來解決定這個問題。

```python
class SingletonRecordFinder:
    def total_population(self, cities):
        result = 0
        for c in cities:
            result += Database().population[c] ## 這裡會直接存取到資料庫
        return result

```

```python
class SingletonTests(unittest.TestCase):
    def test_is_singleton(self):
        db = Database()
        db2 = Database()
        self.assertEqual(db, db2) ## 輸出：OK

    def test_singleton_total_population(self):
        """ This tests on a live database :( """
        rf = SingletonRecordFinder()
        names = ['Seoul', 'Mexico City']
        tp = rf.total_population(names)
        self.assertEqual(tp, 17500000 + 17400000) # what if these change? ## 輸出：OK

    ddb = DummyDatabase()

    def test_dependent_total_population(self):
        crf = ConfigurableRecordFinder(self.ddb) ## Dummy Database
        self.assertEqual( ## 輸出：OK
            crf.total_population(['alpha', 'beta']),
            3
        )

```

```python
Tokyo
33200000
New York
17800000
Sao Paulo
17700000
Seoul
17500000
Mexico City
17400000
Osaka
16425000
Manila
14750000

```

* * *

## 以下未整理

### Python

在 Python 中，並沒有一個單獨的建構函數，但我們可以透過 ` __new__ ` 和 ` __init__ ` 來實作。

- ` __new__ `: This is a static method that is responsible for creating and returning a new instance of a class. It is the first step in the object instantiation process.

- ` __init__ `: This is an instance method that is responsible for initializing an object’s attributes after it has been created by ` __new__ `. The ` __init__ ` method does not return a value and is called automatically after the object is created.

- ` __call__ `: This is an instance method that allows a class’s instances to be called as if they were functions.

接下來我們會實作三種 Singleton Pattern 單例模式：

- GoF 傳統方式的 Singleton Pattern 單例模式

- Simple (or Naive) Python Singleton

- metaclass 方式的 Singleton Pattern 單例模式

* * *

##
