Cooooding!!

Unity(C#)を使ったゲーム開発関連Tipsなど

シングルトンを継承する【C#】

概要

汎用的なクラスをシングルトンとして実装して使おうと考えましたが、状況によっては機能を追加したくなりそうだったためシングルトンの継承をすることを考えてみました。

シングルトンの継承を活用するパターン

シングルトンの継承を活用するパターンは大きく分けて2パターン考えられました。

1.基底も継承先も同じインスタンスとして利用するケース

f:id:nyama41:20190601160937p:plain

サウンドの管理クラスを使って具体例を作ると以下のようになります。
f:id:nyama41:20190601160929p:plain

この例ではライブラリ側もアプリ側もサウンドの管理クラスのシングルトンにアクセスでき、アプリ側はアプリ独自の機能拡張ができて、SoundManagerとMySoundManagerが同じインスタンスなので同時に音が鳴らないように排他制御することも可能になります。

2.継承先ごとに別インスタンスとして利用するケース

f:id:nyama41:20190601155623p:plain

具体例を挙げると以下のようになります。
f:id:nyama41:20190601155636p:plain
この例は単純に機能を共通化しているだけですね。

実装

どちらのパターンでも使えるようにSingletonクラスを定義します。

public abstract class Singleton<SingletonType>
    : System.IDisposable
    where SingletonType : Singleton<SingletonType>, new()
{
    private static SingletonType m_Instance;

    public static SingletonType Instance
    {
        get
        {
            return GetOrCreateInstance<SingletonType>();
        }
    }

    protected static InheritSingletonType GetOrCreateInstance<InheritSingletonType>()
        where InheritSingletonType : class, SingletonType, new()
    {
        if (IsCreated)
        {
            // 基底クラスから呼ばれた後に継承先から呼ばれるとエラーになる。先に継承先から呼ぶ
            if (!typeof(InheritSingletonType).IsAssignableFrom(m_Instance.GetType()))
            {
                UnityEngine.Debug.LogErrorFormat(
                    "{1}が{0}を継承していません",
                    typeof(InheritSingletonType),
                    m_Instance.GetType()
                );
            }
        }
        else
        {
            m_Instance = new InheritSingletonType();
        }
        return m_Instance as InheritSingletonType;
    }

    public static bool IsCreated
    {
        get { return m_Instance != null; }
    }

    public virtual void Dispose()
    {
        m_Instance = default;
    }

    /// <summary>
    /// コンストラクタ(外部からの呼び出し禁止)
    /// </summary>
    protected Singleton() { }
}

1.基底も継承先も同じインスタンスとして利用するケース

パターン1のように使う場合は以下のように使います。

private class BaseClass1 : Singleton<BaseClass1>
{
    /* 基本機能 */
}

private class InheritClass1 : BaseClass1
{
    // 基底クラスのInstanceでは型が基底クラスのものになるので再定義
    public static new InheritClass1 Instance
    {
        get { return GetOrCreateInstance<InheritClass1>(); }
    }

    /* 機能拡張 */
}

このように使う場合、BaseClass1.Instanceよりも先にInheritClass1.Instanceにアクセスして継承先の型でシングルトンを生成する必要があることに注意して下さい。

2.継承先ごとに別インスタンスとして利用するケース

パターン2のように使う場合は以下のように使います。

private abstract class BaseClass2<SingletonType>
    : Singleton<SingletonType>
    where SingletonType : Singleton<SingletonType>, new()
{
    /* 共通機能 */
}

private class InheritClass2A : BaseClass2<InheritClass2A> { /* 機能拡張A */ }

private class InheritClass2B : BaseClass2<InheritClass2B> { /* 機能拡張B */ }

環境

  • Unity 2018.4.0f1
  • VisualStudio 2019