サービスロケーターを作った①

# 始めに
この記事は覚えた内容をアウトプットした記事ですが、日記見たいな気分で書いています。
誤字脱字や覚えた内容が間違っている可能性があるので予めご了承ください。

# 参考にしたリンク先
https://github.com/ayaha401/2DShooting

上のサービスロケーターを参考にして作りました。

# 実装で苦戦した所
・ディクショナリ
・ジェネリック

特にジェネリック部分で苦戦しました。

# サービスロケーターのメリット
・コンポーネント取得が楽になる
・MVPパターン？見たいな使い方が出来る

正直使い方があってるかと言われると自信は無いですが、使った感じはこう思いました。

# サービスロケーターのデメリット
・ロケーター頼りになる
・取得処理で混乱する

コンポーネントをロケーターに登録して、ロケーターからコンポーネントを受け取る。
という使い方をしているので、ロケーターにコンポーネント取得処理が集中していて怖いし、図にすると一転に集まって少し気持ち悪い。

人によってはロケータに登録した番号がぐちゃぐちゃになって混乱する。
散々デメリットを書いていますが、実際に使ってみると便利です。

# 実装したコード
```C#
using System.Collections.Generic;
using UnityEngine;

namespace Utility
{
    public static class Locator<T>
    {
        // コンポーネントの登録を保持する辞書
        private static Dictionary<int, T> instanceDic = new Dictionary<int, T>();

        /// <summary>
        /// 指定したコンポーネントの数が0以上の時trueを返す
        /// </summary>
        public static bool IsExists()
        {
            return instanceDic.Count > 0;
        }

        /* 
         * ここから下のメソッドの引き数idはコンポーネントの登録の番号です。
         * 引き数を何も指定しなければ0が入ります。
         */

        /// <summary>
        /// コンポーネントを登録する
        /// </summary>
        public static void Register(T instance, int id = 0)
        {
            instanceDic[id] = instance;
        }

        /// <summary>
        /// 指定したコンポーネントの登録を解除する
        /// </summary>
        public static void Unregister(int id = 0)
        {
            if (!instanceDic.ContainsKey(id))
            {
                Debug.LogWarning($"{id}番には解除したいコンポーネントが登録されていません。既に解除済みか、IDを間違えている可能性があります。");
                return;
            }

            instanceDic[id] = default;
        }

        /// <summary>
        /// 特定のコンポーネント登録を全て解除する
        /// Locator＜Transform＞とするとディクショナリの全てのTransformが解除される
        /// </summary>
        public static void UnregisterAllInstance()
        {
            instanceDic.Clear();
        }

        /// <summary>
        /// 登録されているコンポーネントを取得する
        /// </summary>
        public static T GetInstance(int id = 0)
        {
            if (!instanceDic.ContainsKey(id))
            {
                Debug.LogWarning($"{id}番には取得したいコンポーネントが登録されていません。登録処理を行っている場合は、IDを間違えている可能性があります。");
                return default;
            }

            return instanceDic[id];
        }
    }
} 

```

# 結局何が出来るか
メリットに書いた通りコンポーネントの取得が楽になります。
同じC#クラスを使いたい時が便利です。

この下の処理はAクラスを利用しているTest_A,Bというクラスがあります。

```C#
using UnityEngine;

public class Test_A : MonoBehaviour
{
    private A a = new A();

    private void Start()
    {
        a.Count = 100;
        Debug.Log(a.Count);
    }
}

public class Test_B : MonoBehaviour
{
    private A a = new A();

    private void Start()
    {
        Debug.Log(a.Count);
    }
}

public class A
{
    public int Count = 0;
}

```

結果はこうなります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588406/e3b51518-3dd0-f66e-9f8b-f94700f5f370.png)
結果が反対になって申し訳ない。
Test_Aでは100という結果に対して、Bは0です。
同じ結果にしたい。という事でコードがこうなります。
```C#
using UnityEngine;
using Utility;

public class Test_A : MonoBehaviour
{
    private A a = new A();

    private void Start()
    {
        // ロケーターにAスクリプトを登録する
        Locator<A>.Register(a);
        a.Count = 100;
        Debug.Log(a.Count);
    }
}

public class Test_B : MonoBehaviour
{
    private void Start()
    {
        // ロケーターに登録されているAスクリプトを取得する
        var a = Locator<A>.GetInstance();
        Debug.Log(a.Count);
    }
}


public class A
{
    public int Count = 0;
}

```

結果はこうなります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588406/b8694799-454d-b14e-5b09-7f27c175d492.png)
登録する処理と取得したい処理が、Start時に同時に動いているので取得処理が先に動いてしまい、何も登録されてないというエラーが出ています。このエラーを回避する為に登録処理を先に行います
```C#
public class Test_A : MonoBehaviour
{
    private A a = new A();

    private void Awake()
    {
        // ロケーターにAスクリプトを登録する
        Locator<A>.Register(a);
        a.Count = 100;
        Debug.Log(a.Count);
    }
}
```
結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588406/013436f5-3c58-3141-1dd9-c18047444a96.png)
エラーが消えたやったー
という訳で、利用する時は必ず登録する処理を先に行いましょう。
登録解除処理は、オブジェクトが破棄された時やシーン切り換え前に行って下さい。