MonoBehaviourをSingleton化するための新手法

MonoBehaviourをSingleton化したいことって、まれによくありますよね。これまでボクは、以下の2つの方法を使っていました。

よくやる方法

その1 Singletonパターンを直接記述

public class TestA : MonoBehaviour
{
	private static TestA entity;
	public static TestA Entity
	{
		get
		{
			if (entity == null)
			{
				entity = UnityEngine.Object.FindObjectOfType<TestA>(true);
				if (entity == null)
				{
					Debug.LogError("ヒエラルキー上に該当GameObjectが存在しない");
				}
			}
			return entity;
		}
	}
	
	//以下、普通のコードたち
}

いわゆるベタ書きです。基本に忠実ではありますが、毎回これを書くのは面倒ということで、「その2」の方法が編み出されました。

その2 継承を使用

ほかの方々の記事があるのでご参照ください。

「MonoBehaviour Singleton」でググって出てくる解説記事は、ほとんどこの継承を使うタイプのものだと思います。

問題点(?)

さて、この2つの方法でも全然問題ないのですが、それぞれの方法の欠点をあえて挙げるならば、

  • 「その1」の欠点 毎回Singletonを記述するのが面倒

  • 「その2」の欠点 MonoBehaviour継承クラスの派生クラスはSingleton化できない

となるかと思います。2つ目が少し分かりにくいので補足すると、

public class TestB : MonoBehaviour
{
	//以下、普通のコードたち
}


//すでにTestBが継承されちゃってるので、
//SingletonMonoBehaviour<T>をさらに継承して使うことはできない(多重継承の禁止)
public class TestBChild : TestB
{
	//以下、普通のコードたち
}

こういうことです。C#は多重継承できないので、この方法だと詰みます。
(C#8.0以降のInterfaceのデフォルト実装機能を使えば行けるかもですが、未確認です)

そこで考えました

つまり、手軽に実装できて、MonoBehaviour継承クラス&その派生クラスすべてに適用可能な方法が求められるわけです(ボクだけ?)
そんな怠惰な人たちにお送りします。こちらの方法をご検討ください。

Singleton強制実装クラスちゃん

public class SingletonAttacher<T> where T : MonoBehaviour
{
	private T entity;
	public T Entity
	{
		get
		{
			if (entity == null)
			{
				entity = UnityEngine.Object.FindObjectOfType<T>(true);
				if (entity == null)
				{
					Debug.LogError("Singleton取得エラー");
				}
			}
			return entity;
		}
	}
}

今回のキモです。前述のクラスすべてに対してSingletonを差し込める機能を持ったクラスとなります。

実装側

public class TestB : MonoBehaviour
{
   //以下、普通のコードたち
}

public class TestBChild : TestB
{
   //Singleton実装
   public static TestBChild Entity => entity.Entity;
   private static SingletonAttacher<TestBChild> entity = new SingletonAttacher<TestBChild>();
   
   //テスト用メソッド
   public void TestMethod()
   {
      Debug.Log("うまくいったね");
   }

   //以下、普通のコードたち
}

Singleton化したいクラスにstaticフィールドとしてSingletonAttacher<T>を加えてやればOKです。

使用側

public class TestC : MonoBehaviour
{
	private void Start()
	{
		TestBChild.Entity.TestMethod();
	}
}

通常のSingletonのときと同じ記述で呼び出せるので、違和感なく使えるかと思います。

おわり

継承ではなく、フィールドを追加するだけであらゆるMonoBehaviour系列クラスをSingleton化できるこの方法、閃いた瞬間、「おいおい自分は神か?」って思ったんですが、いかがでしょうかね??
もっと簡単で完璧な方法をご存知のかたがいらっしゃったら、教えて頂けると嬉しいです。ではではー。

※追記(さらに手軽になりました)

ウェレイさんが、『ジェネリック静的クラス』を用いる改善案を提案してくださいました!ついに実装が1行で済むようになりました!やったー!ありがとうございます!
全体的に諸々調整されていて、完成度はグンと上がっているのではないかと思いますので、ぜひ使ってみてください。
それにしても、これって『ジェネリック静的クラス』のこの上ない使い道ですよね。美しさすら感じるやつです。

Singleton強制実装クラスちゃんver.2

using UnityEngine;
using UnityEngine.Assertions;

public static class SingletonAttacher<T>
	where T : Object
{
	static T _Entity;
	public static T Entity
	{
		get
		{
			if (_Entity == null)
			{
				_Entity = Object.FindObjectOfType<T>(true);
				Assert.IsNotNull(_Entity, $"Singleton取得エラーです。{typeof(T).Name}が存在していません。");
			}
			return _Entity;
		}
	}
}

実装側

public class TestB : MonoBehaviour
{
   //以下、普通のコードたち
}

public class TestBChild : TestB
{
   //Singleton実装
   public static TestBChild Entity => SingletonAttacher<TestBChild>.Entity;

   //テスト用メソッド
   public void TestMethod()
   {
      Debug.Log("うまくいったね");
   }

   //以下、普通のコードたち
}

使用側

public class TestC : MonoBehaviour
{
	private void Start()
	{
		TestBChild.Entity.TestMethod();
	}
}

この記事が気に入ったらサポートをしてみませんか?