仕様書を作っていると
色んなクラスで使える共通の変数を
作りたいと思う場面があるかもしれません。
そういった場合は
シングルトンを利用することが
多いのではないでしょうか?
しかしながら共通の変数として使用するシングルトンは
全てのクラスから実行できる仕組みなので
仕様書の段階で使えるクラスを絞る事ができません。
仕様書の段階で使うクラスを絞れないという事は
バグの原因にもなりえると思います。
そこで、仕様書の段階で
使えるクラスを絞る事ができるシングルトンを
考えてみました。
仕様書の段階で
ClassA は CommonDataA シングルトンを
使用する事ができない
という仕組みを実現します。
この方法でシングルトンを作成すると
共同開発でバグが発生する可能性が減るかもしれませんので
いつもの方法とは違う新鮮な感覚を感じながらでも
流し読みして頂けると幸いです。
また、自己流のやり方のハズですので
批評もどんどん頂けると幸いです。
「〇〇という理由でこの方法は良くない!」
といったコメントはとても参考になるので
批評だからと臆せずに
ガンガンコメントを頂けると助かります。
批評が集まりすぎたら
正しい情報を発信できていないということで
記事を削除するかもしれません
まずは基本のコードを載せます。
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
にアクセスできない事になります。
シングルトンの種類が増えれば増えるほど
継承する必要があるインターフェイスが
増える問題がありますが
使えるクラスを限定できる分
バグが発生する可能性が減るかもしれないので
シングルトンの種類も
ガンガン増やして頂けると良いと思います。
次に応用のコードを載せます。
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 を表示しているのは
どこから実行されたかを知るためです。
この記述をすると、たまに便利になるため
個人的に愛用している手法です。
以上が自己流かもしれませんが
仕様書に強い共通の変数を宣言する方法になります。
使って見れば分かりますが経験則上
仕様書を書くならば
共通の変数の扱いに神経を使う機会が減ります。
よろしければ皆さんも
試して頂けると幸いです。
閲覧ありがとうございました。