
マイケル
みなさんこんにちは!
マイケルです!
マイケルです!

エレキベア
こんにちクマ〜〜〜

マイケル
今日は サービスロケータ(Service Locator)
というデザインパターンを紹介するぜ!
というデザインパターンを紹介するぜ!

エレキベア
なんだそれはクマ

マイケル
簡単に言うと クラス全般から呼ばれるようなサービスクラスを一箇所に集めて
依存関係をまとめることのできるパターン のことだね・・・。
依存関係をまとめることのできるパターン のことだね・・・。

エレキベア
よくわからんクマ・・・

マイケル
便利で手軽に使えるパターンだから、Unityで使って覚えてみよう!
ただ、今はDIコンテナを使う方が推奨されることが多いようだから、その辺も踏まえて解説するね!
ただ、今はDIコンテナを使う方が推奨されることが多いようだから、その辺も踏まえて解説するね!

エレキベア
そこまで便利なら聞いてやるクマ
参考書籍

マイケル
参考書籍は以下になります!
こちらはゲーム開発に特化したデザインパターンを扱った書籍で、WEBでも公開されているから一読の価値ありです!
こちらはゲーム開発に特化したデザインパターンを扱った書籍で、WEBでも公開されているから一読の価値ありです!
Game Programming Patterns ソフトウェア開発の問題解決メニュー impress top gearシリーズ
WEB版:
Game Programming Patterns

マイケル
ちなみにデザインパターン全般については下記記事でも
まとめてあるのでこちらもよければ見てみてください!
まとめてあるのでこちらもよければ見てみてください!

エレキベア
懐かしいクマ〜〜〜
サービスロケータとは
サービスロケータの概要

マイケル
サービスロケータはさっきも言った通り、
クラス全般から呼ばれるようなサービスクラスを一箇所に集めて
依存関係をまとめることのできるパターン のことだ!
クラス全般から呼ばれるようなサービスクラスを一箇所に集めて
依存関係をまとめることのできるパターン のことだ!

マイケル
例えば、いろんなクラスから呼び出されるクラスがあったとして、
これを直接呼び出した場合には下記のように複雑な依存関係になってしまいます。
これを直接呼び出した場合には下記のように複雑な依存関係になってしまいます。

↑直接呼び出した例

マイケル
ここでServiceLocatorクラスに各サービスクラスを登録して
間に挟むことで、依存関係をまとめることができるというわけです!
間に挟むことで、依存関係をまとめることができるというわけです!

↑Service Locatorを挟んだ例

エレキベア
おお〜〜
確かにこれなら綺麗クマ〜〜〜〜
確かにこれなら綺麗クマ〜〜〜〜

マイケル
更に 各サービスクラスのインタフェースを登録して
呼び出し元からはインタフェースを指定するようにすれば、
サービスクラスの差し替えも簡単に行えるようになります!
呼び出し元からはインタフェースを指定するようにすれば、
サービスクラスの差し替えも簡単に行えるようになります!

↑各サービスクラスのインタフェースを登録する

エレキベア
差し替えることができると何うれしいクマ?

マイケル
使うサービスを変更したくなった時に応用が効くのはもちろん、
Dummyクラスに差し替えることでテストができるようにしたり、
開発時にだけログ出力するクラスに差し替える
といったことができるようになるよ!
Dummyクラスに差し替えることでテストができるようにしたり、
開発時にだけログ出力するクラスに差し替える
といったことができるようになるよ!

↑サービスの差し替えが可能

エレキベア
なるほどクマ
それなら柔軟性があっていいクマね
それなら柔軟性があっていいクマね
DIコンテナとどちらを使用すべきか?

マイケル
他に依存性を管理する手法としてDIコンテナが有名です。
ServiceLocatorとDIコンテナどちらを使用するべきか?については下記の記事が分かりやすくまとまっていました!
ServiceLocatorとDIコンテナどちらを使用するべきか?については下記の記事が分かりやすくまとまっていました!
Service LocatorとDependency InjectionパターンとDI Container

マイケル
本来不要なServiceLocatorクラスへの依存が発生してしまうこと、
呼び出し側のコードが少し複雑になることから、
基本的にはDIコンテナの使用が推奨されることが多いみたいです。
呼び出し側のコードが少し複雑になることから、
基本的にはDIコンテナの使用が推奨されることが多いみたいです。

マイケル
ただUnityでDIコンテナを利用するには
ZenjectやVContainerといったDIライブラリを使用することが多く、学習コストと導入コストもそれなりに高いです。
ZenjectやVContainerといったDIライブラリを使用することが多く、学習コストと導入コストもそれなりに高いです。

エレキベア
Zenjectは最近よく聞くクマ〜〜

マイケル
そのため個人レベルで規模が小さい場合にはServiceLocatorを使用し、
慣れてきたら上記のようなDIライブラリを使うことに挑戦するというのも一つの手かと思います!
慣れてきたら上記のようなDIライブラリを使うことに挑戦するというのも一つの手かと思います!

エレキベア
クマはいきなりDIライブラリを使ってみるから
この記事は見なくていいクマ
この記事は見なくていいクマ

マイケル
そうか・・・。
Unityでの実装

マイケル
概要をざっと説明したところで、
実際にコードを書いてみましょう!
ServiceLocatorクラスの例としては下記のようになります!
実際にコードを書いてみましょう!
ServiceLocatorクラスの例としては下記のようになります!
using System;
using System.Collections.Generic;
namespace Services
{
/// <summary>
/// サービスロケータ
/// </summary>
public static class ServiceLocator
{
/// <summary>
/// コンテナ
/// </summary>
private static readonly Dictionary<Type, object> Container;
/// <summary>
/// コンストラクタ
/// </summary>
static ServiceLocator()
{
Container = new Dictionary<Type, object>();
}
/// <summary>
/// サービス取得
/// </summary>
public static T Resolve<T>()
{
return (T) Container[typeof(T)];
}
/// <summary>
/// サービス登録
/// </summary>
public static void Register<T>(T instance)
{
Container[typeof(T)] = instance;
}
/// <summary>
/// サービス登録解除
/// </summary>
public static void UnRegister<T>()
{
Container.Remove(typeof(T));
}
}
}
↑ServiceLocatorクラス
エレキベア
思ったよりシンプルクマね

マイケル
型をkeyとしてDictionaryに登録することで、
呼び出し側からは型を指定して呼び出すことができるようになっているよ。
呼び出し側からは型を指定して呼び出すことができるようになっているよ。

マイケル
例として、下記のPlayerPrefsServiceクラスを登録してみましょう!
using UnityEngine;
namespace Services
{
/// <summary>
/// PlayerPrefsService
/// </summary>
public class PlayerPrefsService : IPlayerPrefsService
{
public void SetInt(string key, int value)
{
PlayerPrefs.SetInt(key, value);
}
public int GetInt(string key)
{
return PlayerPrefs.GetInt(key);
}
}
}
↑PlayerPrefsServiceクラス
namespace Services
{
/// <summary>
/// PlayerPrefsService
/// </summary>
public interface IPlayerPrefsService
{
public void SetInt(string key, int value);
public int GetInt(string key);
}
}
↑PlayerPrefsServiceインタフェース
マイケル
このクラスをプロジェクト初期化等のタイミングでServiceLocatorに登録します。
using Services;
using UnityEngine;
/// <summary>
/// プロジェクト初期化クラス
/// </summary>
public static class ProjectInitializer
{
/// <summary>
/// 初期化処理(シーンのロード前に呼ばれる)
/// </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize()
{
// サービス登録
ServiceLocator.Register<IPlayerPrefsService>(new PlayerPrefsService());
}
}
↑プロジェクト初期化時に登録
マイケル
あとは各クラスでServiceLocator.Resolveを呼び出すことで
各サービスを使用することができます!
各サービスを使用することができます!
using Services;
using UnityEngine;
using Utils;
namespace Scenes.Common
{
/// <summary>
/// PlayerPrefs管理クラス
/// </summary>
public static class SamplePlayerPrefs
{
/// <summary>
/// スコア
/// </summary>
public static int Score
{
get => GetPlayerPrefsIntValue(KeyScore);
set => SetPlayerPrefsIntValue(KeyScore, value);
}
private const string KeyScore = "Score";
private static void SetPlayerPrefsIntValue(string key, int value)
{
ServiceLocator.Resolve<IPlayerPrefsService>().SetInt(key, value);
}
private static int GetPlayerPrefsIntValue(string key)
{
return ServiceLocator.Resolve<IPlayerPrefsService>().GetInt(key);
}
}
}
↑サービスクラスの呼び出し例
エレキベア
呼び出し側も簡単に呼び出せるクマね

マイケル
ちなみにMonoBehaviorクラスを継承したサービスクラスを登録したい場合には、
下記のようにAddComponentした戻り値を登録するようにすればOKです!
下記のようにAddComponentした戻り値を登録するようにすればOKです!
using Services;
using UnityEngine;
/// <summary>
/// プロジェクト初期化クラス
/// </summary>
public static class ProjectInitializer
{
/// <summary>
/// 初期化処理
/// シーンのロード前に呼ばれる
/// </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize()
{
// サービス登録
var serviceLocator = new GameObject("ServiceLocator");
ServiceLocator.Register<IXXXService>(serviceLocator.AddComponent<XXXService>());
}
}
↑MonoBehaviorクラスを登録した例
マイケル
とはいえ共通となるサービスクラスにMonoBehaviorクラスを継承させるのは
極力避けた方がよさそうですね・・・。
極力避けた方がよさそうですね・・・。

エレキベア
やむを得ない時もあるのだろうかクマ・・・。
おわりに

マイケル
というわけで今日はサービスロケータの実装について紹介しました!
どうだったかな??
どうだったかな??

エレキベア
想像していたよりもシンプルで使いやすそうだったクマ
ただ慣れてきたらDIコンテナも使ってみたいクマね
ただ慣れてきたらDIコンテナも使ってみたいクマね

マイケル
シンプルで手軽に使えるサービスロケータ!
呼び出し処理が散らばっている時、テスト処理が書けない時には使ってみてはいかがでしょうか?
呼び出し処理が散らばっている時、テスト処理が書けない時には使ってみてはいかがでしょうか?

マイケル
それでは今日はこの辺で!
アデュー!!!
アデュー!!!

エレキベア
クマ〜〜〜〜〜
コメント