【デザインパターンの勉強記録 # 2】サービスロケーターパターン

# この記事について
デザインパターンについて勉強したアウトプット記事です。
記事の内容でおかしな点や間違っている部分があれば、お手数ですがコメントにてご指摘ください！

# 参考にしたリンク

https://qiita.com/ozaki_shinya/items/9eb0f827caa6a4108888

https://blog.gigacreation.jp/entry/2021/01/24/223859

https://youtu.be/0LC5BgwPKOc?si=qQ49A_F26gxNBEKq

# サービスロケーターパターンとは
簡単に伝えるとシングルトンをより使いやすくした物です。シングルトンはどこからでもアクセスできて、1つだけ存在して欲しいクラスを作る時のデザインパターンです。ただ、シングルトンが増える度にクラス同士の依存関係が滅茶苦茶になってしまうという欠点があります。サービスロケーターとはシングルトンの依存関係をまとめる、どこからでもアクセスできる入れ物(ロケーター)を用意し、外部から他クラスに渡したい物(サービス)を登録したり、解除したりします。依存関係がサービスロケーターに集まることから、シングルトンのデメリットであった依存関係が滅茶苦茶になるという部分を解消することができます。

# 実装したソースコード

https://github.com/hamster3156/ServiceLocator

<details><summary>ソースコード</summary>

```C#
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Hamster.Library
{
    public class ServiceLocator
    {
        // 単一インスタンスを管理するディクショナリ
        private static Dictionary<Type, object> singleInstanceDic = new Dictionary<Type, object>();

        // 都度生成インスタンスを管理するディクショナリ
        private static Dictionary<Type, Type> factoryInstanceDic = new Dictionary<Type, Type>();

        /// <summary>
        /// 指定された型が登録されているか確認する
        /// </summary>
        /// <typeparam name="TCheck">確認したい型名</typeparam>
        /// <returns>登録済みならtrue、存在しなければfalseが返ってくる</returns>
        public static bool IsRegistered<TCheck>()
        {
            Type type = typeof(TCheck);
            return singleInstanceDic.ContainsKey(type) || factoryInstanceDic.ContainsKey(type);
        }

        /// <summary>
        /// 単一インスタンスを登録する
        /// </summary>
        /// <typeparam name="T">登録する型名</typeparam>
        /// <param name="registerInstance">登録するインスタンス</param>
        public static void Register<T>(T registerInstance) where T : class
        {
            singleInstanceDic[typeof(T)] = registerInstance;
        }

        /// <summary>
        /// 都度生成インスタンスを登録する
        /// </summary>
        /// <typeparam name="TRegisterType">登録する型</typeparam>
        /// <typeparam name="TRegisterInstance">登録するインスタンス</typeparam>
        public static void Register<TRegisterType, TRegisterInstance>() where TRegisterType : class
        {
            factoryInstanceDic[typeof(TRegisterType)] = typeof(TRegisterInstance);
        }

        /// <summary>
        /// 登録されている単一または都度生成インスタンスを返す
        /// </summary>
        /// <typeparam name="TGetInstance">取得したいインスタンス</typeparam>
        public static TGetInstance Resolve<TGetInstance>() where TGetInstance : class
        {
            TGetInstance instance = default;
            Type type = typeof(TGetInstance);

            // 登録されていない場合はnullを返す
            if (IsRegistered<TGetInstance>() == false)
            {
                Debug.LogWarning($"{typeof(TGetInstance).Name} はServiceLocatorに登録されていない型です。");
                return instance;
            }

            // 登録されているインスタンスを返す
            if (singleInstanceDic.ContainsKey(type))
            {
                instance = singleInstanceDic[type] as TGetInstance;
            }

            // 登録されているインスタンスを生成して返す
            if (factoryInstanceDic.ContainsKey(type))
            {
                instance = Activator.CreateInstance(factoryInstanceDic[type]) as TGetInstance;
            }

            return instance;
        }

        /// <summary>
        /// 登録したインスタンスを解除する
        /// </summary>
        public static void Unregister<TUnregister>() where TUnregister : class
        {
            Type type = typeof(TUnregister);
            if (singleInstanceDic.ContainsKey(type))
            {
                singleInstanceDic.Remove(type);
            }

            if (factoryInstanceDic.ContainsKey(type))
            {
                factoryInstanceDic.Remove(type);
            }
        }
    }
}

```
</details>

# メリット
・クラス同士の依存関係を疎結合にできる
AがBに依存から、Aがサービスロケーターに依存、Bはサービスロケーターにインスタンスを登録するという流れになるので、クラス同士の依存を避けることができる。

・ピュアC#で利用可能
サービスロケーターを利用すると、ピュアC#でもUnityのGetComponentに近い運用ができるので、コンストラクタの引き数から特定のコンポーネントを取得する！みたいなことをしなくても良くなる。

・単純に分かりやすくて使いやすい
シングルトンを使いやすくしたのがサービスロケーターなので、シングルトンを利用したことがある人なら直ぐに利用できる。

・チーム開発で導入しやすい
上の内容の通り、学習コストが低いためチーム開発でも導入しやすいです。

# デメリット
・結局サービスロケーターに依存している
クラス同士の依存関係は避けたとしても、結局サービスロケーターに依存先を変えただけなので根本的な解決はしていない。

・interfaceで登録しないとコードの安全性が皆無
サービスロケーターはどこからでもアクセスできるという性質上、クラスを直接登録するとどこからでもアクセスできるという欠点があります。interfaceにしないと１つのソースコードの変更によって色んな所でエラーが出る原因になりかねます。

・依存関係が分かりにくい
サービスロケーターに依存していることは分かっても、どのクラスを参照しているのかが分かりにくくなる。つまり、多用しすぎるとソースコードが読みにくくなります。

・登録と解除が手動
少し面倒なのと、登録したインスタンスを解除しないとデータを保持し続けてしまうので、ゲームの動作的には良くなかったり、エラーを巻き起こす原因に繋がる可能性があります。

# どんな時に使うべきか
- クラスの依存関係を疎結合にしたい時
- どうやってこのコンポーネントを取得＆参照するの？っていう時に便利
- ピュアC#でコンポーネントを取得したい！っていう時に便利

# 雑記
全体的にデメリットが多めになっちゃったけど、使ったことがない人や、知らなかったっていう人には本当にオススメしたい。不便さは確かにあるんだけど、便利だなサービスロケーターみたいに感じると思う。

半年前の自分はサービスロケーターとかインターフェース周りでボロボロになっていたと思うと、ちゃんと使えるようになった？んだなと思った。