Singleton pattern

單例模式(Singleton pattern):確保應用程式中只有一個實例,並且提供簡單的全局訪問方式。

然而,單例模式在一些設計模式中被認為弊大於利,被列為反模式(anti-pattern),這是因為單例模式太容易使用了導致開發人員很容易設計出不必要的全域狀態或是全域依賴。

優點

  • 使用簡單,單例是全域可存取的,很容易的就可以取道該單例並在程式中使用。
  • 由於單例是全域可存取因此你不需要去想使否需要暫存它。

缺點

  • 全局存取:單例是全域可存取的,違反了 SOLD原則。
  • 測試困難:單例模式使得測試必須互相獨立變得困難,因為單例可以直接改變狀態。
  • 強依賴性:單例造成的依賴性太強,如果你想更改某個單例,就需要更改所有有使用到它的地方。

以下是一個簡單的單例模式

  • public static Instance用來保存Sence中的單例實體
  • 在Awake()中檢查是否已有這個實例,如果沒有則將其設為實例;如果有則表示有重複設定,需要將它銷毀,以確保在這個場景中只有一個實例。
  • 這個SimpleSingleton有一些缺陷
    • 當載入新Scene時會銷毀這個GameObject(可以使用 DontDestroyOnLoad 告訴Unity不要摧毀它)
    • 在使用時需要將它附加到Scene結構中
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      using UnityEngine;

      public class SimpleSingleton : MonoBehaviour
      {
      public static SimpleSingleton Instance;

      private void Awake()
      {
      if (Instance == null)
      {
      Instance = this;
      }
      else
      {
      Destroy(gameObject);
      }
      }
      }

也可以使用延遲初始化(Lazy initialization)的方式,來建立單例,下面例子中

  • 使用 DontDestroyOnLoad(gameObject) 告訴 Unity 不要銷毀它,因此即使切換場景也不會清除這個單例。
  • 不需要手動附加到Scene結構中,因為使用了 SetupInstance(),如果目前還沒有建立這個單例,它會自動建立一個 GameObject 並附加到它身上。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public class Singleton : MonoBehaviour
    {
    private static Singleton instance;

    public static Singleton Instance
    {
    get
    {
    if (instance == null)
    {
    SetupInstance();
    }
    return instance;
    }
    }

    private void Awake()
    {
    if (instance == null)
    {
    instance = this;
    DontDestroyOnLoad(this.gameObject);
    }
    else
    {
    Destroy(gameObject);
    }
    }

    private static void SetupInstance()
    {
    instance = FindObjectOfType<Singleton>();
    if (instance == null)
    {
    GameObject gameObj = new GameObject();
    gameObj.name = "Singleton";
    instance = gameObj.AddComponent<Singleton>();
    DontDestroyOnLoad(gameObj);
    }
    }
    }

更進一步地將它一般化。在上面的單例模式中,如果需要多個不同的單例,例如 AudioManager 和 GameManager,那麼會複製很多相同的程式碼來建立單例。因此,可以將這個步驟一般化,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
private static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = FindFirstObjectByType<T>();
if (instance == null)
{
SetupInstance();
}
}
return instance;
}
}

public virtual void Awake()
{
RemoveDuplicates();
}
private static void SetupInstance()
{
instance = FindFirstObjectByType<T>();
if (instance == null)
{
GameObject gameObj = new();
gameObj.name = typeof(T).Name;
instance = gameObj.AddComponent<T>();
DontDestroyOnLoad(gameObj);
}
}
private void RemoveDuplicates()
{
if (instance == null)
{
instance = this as T;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}

之後你在使用時,只需要繼承他即可

1
2
3
4
public class GameManager: Singleton<GameManager>
{
// ...
}

Reference: https://github.com/Unity-Technologies/game-programming-patterns-demo/tree/main/Assets/8%20Singleton

評論