Unity公式が公開しているプログラミングパターンを読み取ってみた！～その8. Singleton～

# はじめに
Unity公式がプログラミングパターンのデモを説明書と共に全12種公開していたので、1つずつ読み取っていきたいと思います！
英語の説明書でしたので間違った解釈をしているかもしれませんが、ご了承下さい！
最初の5種は、SOLID原則に沿ったデモとなっており、コードのみが公開されています。
残り7種は、Scene付きのデモが入っていますので、ZIPファイルをダウンロードして試してみてください。
今回は、8つ目のSingletonについてです。

https://github.com/Unity-Technologies/game-programming-patterns-demo

https://resources.unity.com/games/level-up-your-code-with-game-programming-patterns

# 環境
OS : Windows10
Unity バージョン : 2021.3.8f1

# Singletonとは
Singletonとは次の通りです。
- クラスがそれ自体の1つのインスタンスのみをインスタンス化できるようにする
- その単一インスタンスへの簡単なグローバルアクセスを提供する
私自身書きながら何を言ってるのか分からない状態ですが、例を見てみると分かりやすいかもしれません。

```C#
public class SimpleSingleton : MonoBehaviour
{
    public static SimpleSingleton Instance;

    private void Awake()
    {
        if (Instance != null)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
        }
    }
}
```
public static Instanceは、Scene内の1つのインスタンスを保持します。
Awaleメソッド内で既にInstanceが設定されているかを確認し、既に設定済みの場合は破棄するようにしています。これが、「クラスがそれ自体の1つのインスタンスのみをインスタンス化できるようにする」というルールに則ったものになります。
このスクリプトを複数のゲームオブジェクトにアタッチすると、複数の内1つだけが残り、残りは破棄されるようになります。
このInstanceは、シーン内どこからでも呼ばれるグローバル状態になっており、これが「その単一インスタンスへの簡単なグローバルアクセスを提供する」というものを表します。

### 永続性と遅延インスタンス化
上記のままでもSingletonは機能しますが、次の2つの問題点があります。
1. 新しいSceneをロードするとGameObjectが破棄されてしまう
2. Instanceを呼び出す前にセットアップする必要がある
この2つの問題を解決するために次の変更が必要になります。
- DontDestroyOnLoadを使用して永続化する(問題1を解決する)
- 遅延インスタンス化を使用して、最初にシングルトンが必要になったときに自動的にシングルトンを構築する(問題2を解決する)
永続性と遅延インスタンス化を行ったSingletonは次のようになります。
```C#
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);
        }
    }
}
```

Instanceのgetterを作成し、最初にgetterにアクセスした際にSingletonを作成するようにしています。
また、DontDestroyOnLoad(gameObject) は、Sceneの読み込みによってSingletonが破棄されるのを防ぎます。

# デモ
一般的なSingletonの使用方を見ると理解が深まるかもしれません。
デモでは、6台のスピーカーが配置されていて、そのスピーカー一つ一つにSingletonを継承したAudioManager.csがアタッチされています。
ゲームを開始すると各Signleton内でInstanceを生成しようとするのですが、既にScene内に自分以外のSingletonが存在する場合破棄されてしまうため、スピーカーは1台だけになってしまいます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368729/3747693d-1cf2-3ed7-cb75-c4720c1cfe70.png)
　↓ ゲーム開始
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368729/9259cda5-7dc0-ac46-df5a-696d67879e81.png)

デモのSingletonコードは次のようになっています。
ジェネリックにすることにより、AudioManagerだけでなく例えばGameManagerなどにも適用できるようになっています。
```C#
public class Singleton<T> : MonoBehaviour where T : Component
{
    private static T _instance;
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = (T)FindObjectOfType(typeof(T));

                if (_instance == null)
                {
                    SetupInstance();
                }

                return _instance;
            }
        }
    }

    public virtual void Awake()
    {
        RemoveDuplicates();
    }

    private static void SetupInstance()
    {
        _instance = (T)FindObjectOfType(typeof(T));

        if (_instance == null)
        {
            GameObject gameObj = new GameObject();
            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);
        }
    }
}
```

# 利点と欠点
Singletonはその性質上、利点よりも欠点の方が大きいという評判もあります。
導入する際は、欠点をよく理解した上で実装してください。

### 欠点
- Singletonはグローバルアクセスである必要があります。多くの依存関係が発生し、バグのトラブルシューティングが困難になります。
- Singletonは単体テストを困難にします。単体テストは互いに独立している必要がありますが、SingletonはScene全体の多くのGameObjectを変更できるためテストに干渉する可能性があります。
- Singletonは蜜結合を促進します。1つのコンポーネントの変化が大きなバグを生む可能性があります。

### 利点
- Singletonは比較的習得に時間を要しません。
- Singletonは使用しやすいです。

一般的にSingletonは、拡張性を必要としない小規模の開発に向いています。大規模かつ継続的に拡張されていく開発ではSingletonを使用するのは避けた方が良いかもしれません。