デザインパターンとは?

デザインパターンとは、Unityに限らずソフトウェア開発全般において使われる、設計の定石です。

「こういう問題に対して、こう設計すればうまくいく」という経験則から生まれたテンプレートのようなもので、

プログラミング言語に依存せず幅広く活用する事が出来ます。

ただ言語によっては、デザインパターンをより簡単に実装出来たり、逆に複雑になる場合もあります。

シングルトン(Singleton)

シングルトンパターンとは、アプリケーション全体でただ一つだけのインスタンスを保証し、どこからでもアクセスできるグローバルなアクセスポイントを提供するデザインパターンです。

例えば、

  • GameManager
  • SoundManager
  • セーブデータ管理

などどのシーンからでもアクセス出来、必ず一つだけ存在すべきクラスに使われます。

Unityにおけるシングルトンの例(MonoBehaviour)

よく見かける実装ですが、例えば以下の様なクラスです。

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            // DontDestroyOnLoadに指定し、シーンを跨いでも破棄されないようにする
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            // 後から生成されたインスタンスは破棄する
            Destroy(gameObject);
        }
    }
    
    public void StartGame()
    {
        Debug.Log("Game Started");
    }
}

このGameManagerがシーン上のGameObjectにアタッチされていると、

AwakeメソッドでInstanceに現在のGameManagerが代入されます。

その後のDontDestroyOnLoadでシーンをまたいでも破棄されず存在し続けるようになります。

それ以降に別のGameManagerを作ろうとしても、既にInstanceが代入されているため、

後から生成されたインスタンスは全てDestroyされます。

これによりどのシーンやコンポーネントからでも以下のようにメソッドを呼び出すことが出来るようになります。

GameManager.Instance.StartGame();

MonoBehaviourを継承しない場合のシングルトン

MonoBehaviourを継承しない純粋なC#クラスの場合は、以下の様に書けます。

public class SoundManager
{
    public static SoundManager Instance => _instance ??= new SoundManager();
    private static SoundManager _instance;

    // プライベートコンストラクタを使用して、外部からのインスタンス化を防ぐ
    private SoundManager()
    {
    }
    
    public void PlaySound(string soundName)
    {
        Debug.Log($"Playing sound: {soundName}");
    }
}

コンストラクタをprivateにしているので、外部から直接インスタンスを生成することはできません。

また、Instanceプロパティでは??=演算子を使用しており、

_instancenullの場合のみ新しいインスタンスを作成し、

それ以外は既存のインスタンスを返すようになっています。

これにより、Instanceプロパティを通してアクセスする限り、

いつでも同一のインスタンスを参照できるようになります。

シングルトンの注意点

一見便利そうなシングルトンですが、いくつか注意点があります。

依存性が分かりにくくなる

依存性を隠してしまうため、コードの可読性や保守性を下げる原因となる場合があります。

結合度が高くなる

他のクラスがシングルトンクラスに依存するので、コードの変更が波及しやすくなります。

責任が集中しがち

1つのインスタンスが何でもやる状態になりがちです。

うまく責任を分散することを意識しましょう。

再利用性、拡張性が低い

固定的なインスタンスとなるため、後から継承や差し替えが難しいことがあります。

初期化のタイミングが不明瞭

MonoBehaviourを継承している場合、Awake()の実行順に依存しやすく、

他のオブジェクトがシングルトンにアクセスしようとした時にまだ初期化されていない可能性があります。

Script Execution Orderで順序を制御するか、遅延初期化などの対策が必要になる場合があります。

まとめ

今回は、デザインパターンのひとつである「シングルトンパターン」について解説しました。

Unityでは MonoBehaviour を使ったシングルトン実装をよく見かけますし、

ミドルウェアや各種管理クラスなど、実際のプロジェクトでも頻繁に利用されています。

どこからでも同じインスタンスにアクセスできるのは非常に便利ですが、

その反面、注意しなければならない落とし穴も存在します。

便利だからといってなんでもシングルトンにしてしまうのではなく、

本当にその設計が適切か検討した上で、慎重に選択していきましょう。

📣おしらせ!

Unity Asset StoreでICONIC ESSENTIALS SALEが開催中です。 日替わりでお得なアセットが登場します。