Humility

아무리 노력해도 최고가 되지 못할 수 있다⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀그럼에도 노력하는자가 가장 겸손한 것 아닌가

공부하는 블로그

유니티/디자인패턴

[Unity] 유니티 C#) ServiceLocator

새벽_글쓴이 2024. 12. 17. 16:18
반응형

ServiceLocator

서비스나 컴포넌트를 전역적으로 접근할 수 있게 해주는 디자인 패턴

 

구현 방법

1. 서비스로케이터 클래스

public class ServiceLocator
{
    private static ServiceLocator instance;
    private Dictionary<Type, object> services;

    public static ServiceLocator Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new ServiceLocator();
            }
            return instance;
        }
    }

    private ServiceLocator()
    {
        services = new Dictionary<Type, object>();
    }

    public void RegisterService<T>(T service)
    {
        Type type = typeof(T);
        if (services.ContainsKey(type))
        {
            Debug.LogWarning($"Service of type {type} is already registered!");
            return;
        }
        services[type] = service;
    }

    public T GetService<T>() where T : class
    {
        Type type = typeof(T);
        if (!services.ContainsKey(type))
        {
            Debug.LogError($"Service of type {type} not found!");
            return null;
        }
        return services[type] as T;
    }

    public void UnregisterService<T>()
    {
        Type type = typeof(T);
        if (services.ContainsKey(type))
        {
            services.Remove(type);
        }
    }
}

 

2. 서비스 인터페이스

public interface IScoreService
{
    int GetScore();
    void AddScore(int amount);
}

// 실제 서비스 구현
public class ScoreService : IScoreService
{
    private int currentScore = 0;

    public int GetScore() => currentScore;
    public void AddScore(int amount) => currentScore += amount;
}

 

3. 사용 예시

public class GameManager : MonoBehaviour
{
    void Start()
    {
        // 서비스 등록
        ServiceLocator.Instance.RegisterService<IScoreService>(new ScoreService());
    }

    void UseService()
    {
        // 서비스 사용
        var scoreService = ServiceLocator.Instance.GetService<IScoreService>();
        scoreService.AddScore(100);
        Debug.Log($"Current Score: {scoreService.GetScore()}");
    }
}

 

핵심개념

  • 서비스 로케이터는 애플리케이션 전체에서 필요한 서비스들을 중앙에서 관리하고 제공하는 패턴입니다
  • 싱글톤 패턴을 기반으로 하여 전역적인 접근점을 제공합니다
  • 인터페이스를 통한 느슨한 결합을 지향합니다

 

사용목적

  • 서비스 의존성을 중앙에서 관리하여 코드 유지보수성 향상
  • 런타임에 서비스 구현체를 유연하게 교체 가능
  • 테스트 용이성 확보 (Mock 객체 사용 가능)
  • 컴포넌트 간 직접적인 참조 없이 통신 가능

 

사용사례 및 예시코드

공통으로 사용할 서비스로케이터

public class ServiceLocator
{
    private static ServiceLocator instance;
    private Dictionary<Type, object> services = new Dictionary<Type, object>();

    public static ServiceLocator Instance => instance ??= new ServiceLocator();

    public void RegisterService<T>(T service) => services[typeof(T)] = service;
    
    public T GetService<T>() where T : class
    {
        if (services.TryGetValue(typeof(T), out object service))
            return service as T;
        throw new Exception($"Service {typeof(T).Name} not found!");
    }
}

 

1.게임 시스템관리

public interface IGameSystem
{
    void StartGame();
    void SaveGame();
    int GetScore();
    void AddScore(int points);
}

public class GameSystem : IGameSystem
{
    private int currentScore = 0;
    
    public void StartGame()
    {
        currentScore = 0;
        Debug.Log("Game Started!");
    }

    public void SaveGame()
    {
        PlayerPrefs.SetInt("Score", currentScore);
        Debug.Log("Game Saved!");
    }

    public int GetScore() => currentScore;
    
    public void AddScore(int points)
    {
        currentScore += points;
        Debug.Log($"Score updated: {currentScore}");
    }
}

 

오디오시스템

public interface IAudioSystem
{
    void PlayBGM(string trackName);
    void PlaySFX(string sfxName);
    void SetVolume(float volume);
}

public class AudioSystem : IAudioSystem
{
    private Dictionary<string, AudioClip> audioClips = new Dictionary<string, AudioClip>();
    private float masterVolume = 1.0f;

    public void PlayBGM(string trackName)
    {
        Debug.Log($"Playing BGM: {trackName}");
        // BGM 재생 로직
    }

    public void PlaySFX(string sfxName)
    {
        Debug.Log($"Playing SFX: {sfxName}");
        // 효과음 재생 로직
    }

    public void SetVolume(float volume)
    {
        masterVolume = Mathf.Clamp01(volume);
        Debug.Log($"Volume set to: {masterVolume}");
    }
}

 

UI관리

public interface IUISystem
{
    void ShowScreen(string screenName);
    void ShowPopup(string popupName);
    void HidePopup(string popupName);
}

public class UISystem : IUISystem
{
    private Dictionary<string, GameObject> screens = new Dictionary<string, GameObject>();
    private Stack<GameObject> activePopups = new Stack<GameObject>();

    public void ShowScreen(string screenName)
    {
        Debug.Log($"Showing screen: {screenName}");
        // 화면 전환 로직
    }

    public void ShowPopup(string popupName)
    {
        Debug.Log($"Showing popup: {popupName}");
        // 팝업 표시 로직
    }

    public void HidePopup(string popupName)
    {
        Debug.Log($"Hiding popup: {popupName}");
        // 팝업 숨김 로직
    }
}

 

네트워크 통신

public interface INetworkSystem
{
    void Connect(string serverUrl);
    void SendData(string endpoint, string data);
    void Disconnect();
}

public class NetworkSystem : INetworkSystem
{
    private bool isConnected = false;

    public void Connect(string serverUrl)
    {
        isConnected = true;
        Debug.Log($"Connected to server: {serverUrl}");
    }

    public void SendData(string endpoint, string data)
    {
        if (!isConnected)
        {
            Debug.LogError("Not connected to server!");
            return;
        }
        Debug.Log($"Sending data to {endpoint}: {data}");
    }

    public void Disconnect()
    {
        isConnected = false;
        Debug.Log("Disconnected from server");
    }
}

 

게임매니저 ( 실행부 )

public class GameManager : MonoBehaviour
{
    private void Start()
    {
        // 모든 서비스 등록
        ServiceLocator.Instance.RegisterService<IGameSystem>(new GameSystem());
        ServiceLocator.Instance.RegisterService<IAudioSystem>(new AudioSystem());
        ServiceLocator.Instance.RegisterService<IUISystem>(new UISystem());
        ServiceLocator.Instance.RegisterService<INetworkSystem>(new NetworkSystem());
        
        // 게임 시작
        InitializeGame();
    }

    private void InitializeGame()
    {
        // 게임 시스템 초기화
        var gameSystem = ServiceLocator.Instance.GetService<IGameSystem>();
        gameSystem.StartGame();

        // 오디오 시스템 설정
        var audioSystem = ServiceLocator.Instance.GetService<IAudioSystem>();
        audioSystem.PlayBGM("MainTheme");
        audioSystem.SetVolume(0.8f);

        // UI 초기화
        var uiSystem = ServiceLocator.Instance.GetService<IUISystem>();
        uiSystem.ShowScreen("MainMenu");

        // 네트워크 연결
        var networkSystem = ServiceLocator.Instance.GetService<INetworkSystem>();
        networkSystem.Connect("game.server.com");
    }

    // 게임 플레이 예시
    private void OnPlayerScore(int points)
    {
        var gameSystem = ServiceLocator.Instance.GetService<IGameSystem>();
        gameSystem.AddScore(points);

        var audioSystem = ServiceLocator.Instance.GetService<IAudioSystem>();
        audioSystem.PlaySFX("ScoreSound");

        if (points > 100)
        {
            var uiSystem = ServiceLocator.Instance.GetService<IUISystem>();
            uiSystem.ShowPopup("HighScorePopup");
            
            var networkSystem = ServiceLocator.Instance.GetService<INetworkSystem>();
            networkSystem.SendData("scores/submit", gameSystem.GetScore().ToString());
        }
    }
}

 

결론

장점

  • 전역적인 접근성 - 어디서든 서비스에 접근 가능
  • 의존성 관리 용이
  • 서비스 교체가 쉬움 (인터페이스 기반)
  • 테스트 용이성 (Mock 객체로 쉽게 교체 가능)

단점

  • 전역 상태를 만들어 코드의 결합도가 높아질 수 있음
  • 서비스 의존관계가 명시적이지 않음
  • 싱글톤 패턴의 단점을 일부 공유
각 시스템들은 ServiceLocator를 통해 쉽게 접근하고 관리할 수 있으며,
독립적으로 동작하면서도 필요할 때 서로 협력할 수 있게 만든다
반응형