본문 바로가기

유니티/디자인 패턴

[Unity] 서비스 로케이터 패턴 (Service Locator Pattern)

반응형

서비스 로케이터 패턴이란?

이 디자인 패턴은 간단히 말하면 여러 개의 서비스를 서비스 로케이터라고 부르는 중앙 처리기가 관리해 주는 디자인 패턴이다.

 

서비스 로케이터 표현은 마치 114같은 존재이다. 114로 전화를 걸면 특정 상호의 주소, 번호, 이름 등을 알 수 있다. 우리가 특정 정보에 직접 접근할 필요 없이 서비스 로케이터가 대신 이 과정을 처리해 준다. 물론 그만큼 서비스 로케이터의 무게는 커질 것이다.

 

초기화 작업 및 직접적인 종속성 문제를 서비스 로케이터가 관리하기 때문에 사용하는 클라이언트 클래스 입장에서는 이를 벗어날 수 있다.

 

클래스 다이어그램


왜 사용하는 것일까?

서비스 로케이터 패턴를 사용하는 이유 중 하나는 스크립트 간의 종속성을 낮추고 관리를 쉽게 해 준다는 데에 있다.

특히 서비스를 인터페이스로 등록하여 사용하면 개발 초기부터 쉽게 사용하다는 장점이 있다.

 

반면 이러한 종속성을 감추는 행위가 불리하게 작용할 수도 있다. (마치 싱글톤 처럼) 클래스의 추적이 어려워져 유지 보수가 결과적으로 어려워지는 것이다.

 

따라서 남발하지 않고 적절하게 사용하는것이 중요하다.

(싱글톤과 유사하게 소규모 프로젝트나 프로토타입에만 사용한다거나 중간 이벤트 채널등을 별도로 두어 사용하는 것이 좋은 것 같다.)

 

장점

  1. 간단한 종속성 관리 : 상대적으로 간단하게 프로젝트의 종속성을 관리
  2. 최적화 : 런타임시에 활용되기 때문에 메모리의 낭비를 줄여 런타임 시점에 최적화 가능

단점

  1. 전역 종속성 문제 : 남발할 경우 서비스 로케이터에 너무 의존하게 되어 전역 종속성 문제 발생
  2. 블랙박스화 : 순환 참조 혹은 Null 참조 등 서비스 로케이터에 종속성 이슈가 발생할 경우 추적과 대처가 어려움

어떻게 사용할 수 있을까?

기본 구조는 간단하다.

 

등록될 서비스와 이를 관리할 서비스 로케이터, 그리고 이를 사용할 클라이언트로 나뉜다.

 

먼저 등록할 서비스들은 인터페이스와 이를 상속받은 구체 클래스로 작성한다.

    public interface ISpecialLog
    {
        void Print(string message);
    }

    public interface IRewardSystem
    {
        void SendReward(int reward);
    }
    
    public interface ITimer
    {
        void Tick(float deltaTime);
    }

    public class SpecialLog : ISpecialLog
    {
        public void Print(string message)
        {
            Debug.Log($"<color=red>{message}</color>");
        }
    }

    public class RewardSystem : IRewardSystem
    {
        public void SendReward(int reward)
        {
            Debug.Log("sent somewhere");
        }
    }

    public class Timer : ITimer
    {
        public void Tick(float deltaTime)
        {
            Debug.Log("Time Passed");
        }
    }

 

위의 클래스들은 서비스들로 등록이 될 것이다.

 

이제 서비스 제공자와 사용자 사이에 프락시(Proxy) 역할을 하는 서비스 로케이터를 작성하면 다음과 같다.

 

public static class ServiceLocator
{
    public static IDictionary<Type, object> Services { get => _services; }
    private static Dictionary<Type, object> _services = new();

    public static void Register<T>(T service)
    {
        if (!_services.ContainsKey(typeof(T)))
        {
            _services[typeof(T)] = service;
        }
    }

    public static T Get<T>()
    {
        return (T)_services[typeof(T)];
    }
}

 

전역 변수로 설정하여 쉽게 접근이 가능하도록 하는 것이 좋다.

아래는 위의 서비스 로케이터를 사용하는 클라이언트 스크립트이다.

 

public class Client : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        ISpecialLog log = new SpecialLog();
        ServiceLocator.Register(log);

        ServiceLocator.Get<ISpecialLog>().Print("Hello World");
    }
}

 

간단히 서비스를 등록하고 실행하면 아래와 같은 결과를 얻을 수 있다.

 

실행 결과

 

반응형