
의존성 주입을 하려고 보니.. 다른 씬에서도 의존성이 있는 객체가 있음에도 불구하고 씬을 이동하면 객체가 파괴되는 유니티의 성질 때문에 골머리를 썩혔다.
DontDestroyOnLoad와 싱글턴 패턴을 사용하지 않으려다 보니 남은 것은 static 밖에 없었다.
그리고 이를 조금 더 찾아본 결과 서비스 로케이터 패턴이라고 정형화된 패턴이 있었고, 이를 공부하고 정리하여 실제 프로젝트에 적용해보고자 했다.
서비스 로케이터(Service Locator) 패턴의 목표는 모듈화 수준을 높이는 것이다.
클라이언트와 인터페이스 사이의 의존성을 제거하는 방식이다.

서비스 로케이터는 중앙 레지스트리같은 역할을 하는데, 서로 다른 인터페이스에 대한 구현체를 저장하고, 사용하도록 돕는다.
결과적으로 클라이언트는 서비스 로케이터를 통해 구현체를 가져오기 때문에 구현체와 더 느슨한 관계를 가지게 된다.
1. 간단한 의존성 처리
서비스 로케이터가 의존성을 관리하기 때문에 클래스 간의 결합도를 낮춘다.
2. 최적화
런타임에 활용되기 때문에 메모리 낭비를 줄여 런타임 시점에 최적화가 가능하다.
1. 전역 종속성
서비스 로케이터는 static으로 선언되기 때문에 전역적으로 의존성 문제가 발생할 수도 있다.
2. 블랙박스화
순환 참조, 널 참조 등 서비스 로케이터에 종속성 문제가 발생한 경우, 코드의 추적이 어렵다.
1. 등록될 서비스를 인터페이스와 구체 클래스로 나눈다.
using UnityEngine;
public interface ILog
{
void Print(string message);
}
public class WarningLog
{
void Print(string message)
{
Debug.Log($"<color=yellow>{message}</color>");
}
}
2. 서비스 제공 클래스와 클라이언트 사이에서 프록시 역할을 하는 서비스 로케이터를 만든다.
public static class ServiceLocator
{
private static Dictionary<Type, object> m_services = new();
public static IDictionary<Type, object> Services { get => m_services; }
// 선택적이며, 미리 서비스 목록을 지정해두고 초기화하는 방식으로도 등록할 수 있다.
public static void Initialize()
{
Register<ILog>(new WarningLog());
// 다른 서비스들도 충분히 추가할 수 있다.
}
public static void Register<T>(T service)
{
if(!m_services.ContainsKey(typeof(T))
{
m_services[typeof(T)] = service;
}
}
public static T Get<T>()
{
return (T)m_services[typeof(T)];
}
}
3. 클라이언트 클래스에서 서비스 로케이터를 통해 느슨한 관계를 가짐을 확인한다.
using UnityEngine;
public class Client : MonoBehaviour
{
private ILog m_log;
private void Awake()
{
m_log = ServiceLocator.Get<ILog>();
}
prifvate void Start()
{
m_log?.Print("This is Service Locator.");
}
}
사실, 구체화 클래스를 필요에 따라 변경하여 등록할 수 있다는 점만 제외한다면 싱글턴 클래스 내부에 서비스들을 생성하고 속성으로 빼면 똑같은 것이 아닌가? 라는 생각은 있다.
뭐 전역적으로 접근한다는 점에서의 싱글턴만 사용하는 나니깐 그럴 수도 있다.
static 클래스이기 때문에 생성자도 사용할 수 없어서 Init()과 같은 정적 초기화 메서드를 어디서 호출해야만 하는데 결국에 이걸 초기화하는 위치가 그럴듯한게 GameManager인거 같은데..