サービスロケーターパターン(Service Locator Pattern)は、クラスが必要とするサービス(インターフェースの実装)を取得するためのデザインパターンです。
サービスロケータは、アプリケーション全体で使用される依存関係を集中管理し、クライアントコードが具体的なサービスのインスタンスを直接生成することを防ぎます。
例えば、いろんなクラスから呼び出されるクラスがあったとして、 これを直接呼び出した場合には下記のように複雑な依存関係になってしまいます。(Opsを直接呼び出している例)
そこで、ServiceLocatorクラスに各サービスクラスを登録。間を挟むことで、依存関係をまとめる
各サービスクラスのインタフェースを登録して、呼び出し元からはインタフェースを指定するようにすれば、サービスクラスの差し替えも簡単に行えるようになります
差し替えることができると、 使うサービスを変更したくなった時に応用が効くのはもちろん、 Dummyクラスに差し替えることでテストができるようになったり、 開発時にだけログ出力するクラスに差し替える といったことができるようになります。
他に依存性を管理する手法としてDIコンテナが有名です。 ServiceLocatorとDIコンテナ、どちらを使用するべきか?についてはネット上の記事等を参考にしてみてください。
Service Locator と Dependency Injectionパターン と DI Container
本来不要なServiceLocatorクラスへの依存が発生してしまうこと、呼び出し側のコードが少し複雑になることなどから、DIコンテナの使用が推奨されることがあります。
ただ、UnityでDIコンテナを利用するには、ZenjectやVContainerといったDIライブラリを使用することが多く、学習コストと導入コストもそれなりに高いです。
そのため、個人レベルや規模が小さい場合にはServiceLocatorを使用し、慣れてきたら上記のようなDIライブラリを使うことに挑戦するというのも一つの手かと思います。
型をkeyとしてDictionaryに登録することで、
呼び出し側からは型を指定して呼び出すことができるようになる。
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));
}
}
}
例として、 PlayerPrefsService
クラスを登録してみる
namespace Services
{
public interface IPlayerPrefsService
{
public void SetInt(string key, int value);
public int GetInt(string key);
}
}
using UnityEngine;
namespace Services
{
public class PlayerPrefsService : IPlayerPrefsService
{
public void SetInt(string key, int value)
{
PlayerPrefs.SetInt(key, value);
}
public int GetInt(string key)
{
return PlayerPrefs.GetInt(key);
}
}
}
プロジェクト初期化時、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);
}
}
}
↓だけ適当なGameObjectにアタッチしてみて(他はフォルダにあれさえすればいい)
using Scenes.Common;
using UnityEngine;
public class ExampleUsage : MonoBehaviour
{
void Start()
{
// スコアの設定
SamplePlayerPrefs.Score = 42;
// スコアの取得
int score = SamplePlayerPrefs.Score;
Debug.Log($"Current Score: {score}");
}
}
サービスロケーターパターンのシンプルな例。
サービスインターフェース。
指定された soundName を再生するためのメソッドです。
public interface IAudioService {
void PlaySound(string soundName);
}
具体的なサービスクラス
using UnityEngine;
public class AudioService : IAudioService {
public void PlaySound(string soundName) {
Debug.Log("Playing sound: " + soundName);
// 実際のサウンド再生ロジックを書く
}
}
サービスロケーター。静的クラスとして定義され、サービスの登録と取得を行います。
using System;
using System.Collections.Generic;
public static class ServiceLocator {
private static Dictionary<Type, object> services = new Dictionary<Type, object>();
public static void RegisterService<T>(T service) {
var type = typeof(T);
if (!services.ContainsKey(type)) {
services[type] = service;
}
}
public static T GetService<T>() {
var type = typeof(T);
if (services.ContainsKey(type)) {
return (T)services[type];
}
throw new Exception("Service not found: " + type);
}
}
services は Type をキーとし、サービスのインスタンスを値とする辞書。
RegisterService<T>
メソッドは、サービスインスタンスがすでに登録されていない場合のみ登録します。
GetService<T>
メソッドは、登録されたサービスインスタンスを取得します。サービスが見つからない場合は例外をスローします。
サービスロケーターの使用例(MonoBehaviour を継承し、Unity のゲームオブジェクトとして機能します。)
下のスクリプトだけ適当なGameObjectにアタッチしてください。
using UnityEngine;
public class GameManager : MonoBehaviour {
void Start() {
// サービスの登録
ServiceLocator.RegisterService<IAudioService>(new AudioService());
// サービスの取得と使用
var audioService = ServiceLocator.GetService<IAudioService>();
audioService.PlaySound("BackgroundMusic");
}
}
Start メソッド内で、AudioService のインスタンスを ServiceLocator に登録します。
登録後、ServiceLocator.GetService