【Unity】仕様書に強い共通の変数を宣言する

仕様書を作っていると
色んなクラスで使える共通の変数を
作りたいと思う場面があるかもしれません。

そういった場合は
**シングルトン**を利用することが
多いのではないでしょうか？

しかしながら共通の変数として使用するシングルトンは
全てのクラスから実行できる仕組みなので
仕様書の段階で使えるクラスを絞る事ができません。

仕様書の段階で使うクラスを絞れないという事は
バグの原因にもなりえると思います。

そこで、仕様書の段階で
使えるクラスを絞る事ができるシングルトンを
考えてみました。

**仕様書の段階で**
**ClassA は CommonDataA シングルトンを**
**使用する事ができない**
**という仕組みを実現します。**

この方法でシングルトンを作成すると
共同開発でバグが発生する可能性が減るかもしれませんので
いつもの方法とは違う新鮮な感覚を感じながらでも
流し読みして頂けると幸いです。

また、自己流のやり方のハズですので
批評もどんどん頂けると幸いです。
「〇〇という理由でこの方法は良くない！」
といったコメントはとても参考になるので
批評だからと臆せずに
ガンガンコメントを頂けると助かります。
~~批評が集まりすぎたら~~
~~正しい情報を発信できていないということで~~
~~記事を削除するかもしれません~~

<hr>

まずは基本のコードを載せます。

```Csharp:基本のコード
namespace CompanyName.CommonData
{
    /// <summary>
    /// 共通変数。
    /// ICommonData を継承したクラスでのみ使用可能。
    /// 継承するクラスを指定するタイプの仕様書に強い仕組み。
    /// こちらは data が protected なパターンで肥大化しないため使いやすい。
    /// </summary>
    public interface ICommonData
    {
        protected static readonly CommonData data = new(); //static フィールドを極力減らすためこの記述を行う。 ここは protected も指定可能。こちらの方が肥大化しない。

        public class CommonData
        {
            //経過ターン数
            public int NowTurnCount { get; set; } = 0;

            //特定の処理を実行しないためのフラグ1
            public bool IsNotExecuteSpecificProcess1 { get; set; } = false;

            //特定の処理を実行しないためのフラグ2
            public bool IsNotExecuteSpecificProcess2 { get; set; } = false;

            //特定の処理を実行しないためのフラグ3
            public bool IsNotExecuteSpecificProcess3 { get; set; } = false;
        }

        //継承せずとも使用したいプロパティやメソッドは public static を指定する。(非推奨)
        public static int GetTurnCount()
        {
            return data.NowTurnCount;
        }
    }
}

namespace CompanyName
{
    class ClassA : CommonData.ICommonData
    {
        static void ClassAFunction()
        {
            CommonData.ICommonData.data.NowTurnCount = 3; //OK
            CommonData.ICommonData.GetTurnCount(); //OK
        }
    }

    class ClassB
    {
        static void ClassBFunction()
        {
            //CommonData.ICommonData.data.NowTurnCount = 3; //コンパイルエラー
            CommonData.ICommonData.GetTurnCount(); //OK
        }
    }
}
```

という訳で解説していきます。

~~シングルトンっぽくないですが~~
~~これはシングルトンであるという認識でお願いします。~~

このシングルトンはインターフェイスで作成することで
継承したクラスでしか使えないようにする仕組みです。
インターフェイスなので、多重継承もできます。

仕様書に継承するクラス・インターフェイスを
羅列する事が前提条件にはなりますが
この仕組みならばシングルトンを使うクラスを
仕様書作成の段階で指定する事ができます。

コードを見てみると

**ClassA** は **CommonData.ICommonData** を継承しています。なので
**CommonData.ICommonData.data**
にアクセスすることができます。

しかしながら **ClassB** は **CommonData.ICommonData** を継承していません。なので
**CommonData.ICommonData.data**
にアクセスすることができません。

仕様書に書かれていないインターフェイスを
継承する事は原則ありませんので
**仕様書の段階で ClassB は**
**CommonData.ICommonData.data**
**にアクセスできない事になります。**

シングルトンの種類が増えれば増えるほど
継承する必要があるインターフェイスが
増える問題がありますが
使えるクラスを限定できる分
バグが発生する可能性が減るかもしれないので
シングルトンの種類も
ガンガン増やして頂けると良いと思います。

<hr>

次に応用のコードを載せます。

```Csharp:応用のコード
namespace CompanyName.CommonData
{
    /// <summary>
    /// プレイヤーの共通変数。
    /// IPlayerData を継承したクラスでのみ使用可能。
    /// 継承するクラスを指定するタイプの仕様書に強い仕組み。
    /// こちらは data が private なパターンで肥大化してしまうがより安全な方法。
    /// </summary>
    public interface IPlayerData
    {
        private static readonly PlayerData data = new(); //static フィールドを極力減らすためこの記述を行う。 private はより強力に制限をかける記述。

        public class PlayerData
        {
            public int hp { get; set; } = 32;
            public int maxHP { get; set; } = 32;
        }

        // data が private なので、アクセス可能にするためにプロパティを実装する
        protected static int HP
        {
            get
            {
                return data.hp;
            }
            set
            {
                data.hp = System.Math.Clamp(value, 0, data.maxHP);
                //ShowLogIPlayerData(); //デバック用
            }
        }

        protected static int MaxHP
        {
            get
            {
                return data.maxHP;
            }
            set
            {
                if (0 < value)
                {
                    data.maxHP = value;
                    if (data.maxHP < data.hp)
                    {
                        data.hp = data.maxHP;
                    }
                }
            }
        }

        //便利なメソッドも公開範囲を指定して作成できる。
        protected static void CauseDamage(int damage)
        {
            HP -= damage;
        }

        //継承せずとも使用したいプロパティやメソッドは public static を指定する。(非推奨)
        public static void ShowLog()
        {
            try
            {
                throw new System.Exception();
            }
            catch (System.Exception e)
            {
                UnityEngine.Debug.Log($"HP : {data.hp}, MaxHP : {data.maxHP}, Hierarchy is \n{e.StackTrace}");
            }
        }
    }
}

namespace CompanyName
{
    class ClassA : CommonData.IPlayerData
    {
        static void ClassAFunction()
        {
            CommonData.IPlayerData.HP = 100; //OK
            CommonData.IPlayerData.CauseDamage(5); //OK
            CommonData.IPlayerData.ShowLog(); //OK
        }
    }

    class ClassB
    {
        static void ClassBFunction()
        {
            //CommonData.IPlayerData.HP = 100; //コンパイルエラー
            //CommonData.IPlayerData.CauseDamage(5); //コンパイルエラー
            CommonData.IPlayerData.ShowLog(); //OK
        }
    }
}
```

応用のコードは記述量が肥大化しています。
しかしながら
CommonData.IPlayerData.data
にアクセスする方法が
ゲッターとセッターに限定されています。
肥大化する反面
安全性は高いと言って良いと感じます。

ログを出すときに
Exception を throw して
StackTrace を表示しているのは
どこから実行されたかを知るためです。
この記述をすると、たまに便利になるため
個人的に愛用している手法です。

<hr>

以上が自己流かもしれませんが
仕様書に強い共通の変数を宣言する方法になります。

使って見れば分かりますが経験則上
仕様書を書くならば
共通の変数の扱いに神経を使う機会が減ります。

よろしければ皆さんも
試して頂けると幸いです。
閲覧ありがとうございました。