블로그 포스팅 형식에 맞게 서비스 로케이터 패턴을 작성해드릴게요!
블로그 글 형식에 맞게 서비스 로케이터 패턴 포스팅을 작성해드릴게요!
서비스 로케이터 패턴(Service Locator Pattern) — Unity 게임 개발에서의 활용
예전에 플레이어블 광고 게임 프로젝트를 진행하면서, SoundManager, ObjectPool, GameEventBus 등 여러 매니저 클래스들을 각 시스템이 직접 참조해야 하는 상황이 생겼는데요. 그때마다 FindObjectOfType<>()이나 싱글톤 남용으로 코드가 지저분해지는 걸 느꼈어요. 이 문제를 깔끔하게 해결해준 게 바로 서비스 로케이터 패턴(Service Locator Pattern) 이었습니다!
1. 서비스 로케이터 패턴이란?
서비스(의존성)를 어디서든 등록하고 꺼내 쓸 수 있는 중앙 레지스트리를 두는 디자인 패턴입니다.
이 구조에서 등장하는 세 가지 역할이 있어요.
- Service Interface : 시스템이 사용할 기능의 추상 계약 (인터페이스 또는 추상 클래스)
- ServiceLocator : 서비스를 등록하고, 요청하면 꺼내주는 중앙 레지스트리
- Client : ServiceLocator에 서비스를 요청해서 사용하는 객체
비유하자면, 호텔 컨시어지와 똑같아요. 손님(Client)은 컨시어지(ServiceLocator)에게 "렌터카 업체 알려줘!"라고 요청하면, 컨시어지가 적절한 업체(Service)를 연결해주는 구조입니다!
2. 왜 서비스 로케이터 패턴을 쓸까?
제가 이 패턴을 쓰게 된 이유는 크게 네 가지입니다.
- 의존성 역전 → 클라이언트는 구체 구현체가 아닌 인터페이스만 알면 됩니다.
- 교체 용이성 → 테스트 시 Mock 서비스로 교체하는 게 아주 간단해집니다.
- 전역 접근 단순화 → 싱글톤처럼 FindObjectOfType<>() 없이 어디서든 서비스에 접근할 수 있습니다.
- 런타임 바인딩 → 앱 실행 중에도 서비스를 교체하거나 재등록할 수 있어 유연한 구조를 만들 수 있습니다.
즉, 의존성을 코드 안에 "박아넣는" 대신 중앙에서 관리하게 되어, 규모가 커질수록 진가가 드러나는 패턴입니다!
3. 사용 방법
Unity C# 기준으로 간단하게 구현하는 방법을 소개할게요.
① 서비스 인터페이스 정의
public interface IAudioService
{
void Play(string clipName);
void Stop();
}
② ServiceLocator 작성
public static class ServiceLocator
{
private static readonly Dictionary<Type, object> _services = new();
public static void Register<T>(T service)
{
_services[typeof(T)] = service;
}
public static T Get<T>()
{
if (_services.TryGetValue(typeof(T), out var service))
return (T)service;
throw new Exception($"Service {typeof(T).Name} not registered!");
}
public static void Unregister<T>()
{
_services.Remove(typeof(T));
}
}
③ 구현체 작성
public class SoundManager : MonoBehaviour, IAudioService
{
public void Play(string clipName)
{
Debug.Log($"사운드 재생: {clipName}");
// 실제 AudioSource 처리
}
public void Stop()
{
Debug.Log("사운드 정지");
}
}
④ 등록 및 사용
// 초기화 시점 (e.g. GameBootstrapper)
ServiceLocator.Register<IAudioService>(soundManager);
// 어느 클래스에서든
ServiceLocator.Get<IAudioService>().Play("bgm_main");
Unity에서는 Awake()나 별도의 Bootstrapper 스크립트에서 서비스를 등록해두면, 씬 어디서든 깔끔하게 꺼내 쓸 수 있어요!
4. 실제 적용 예시
플레이어블 광고 게임에서 NPCStateMachine이 사운드와 이벤트 버스를 필요로 하는 상황을 가정해볼게요.
적용 전 : NPCStateMachine 안에서 SoundManager.Instance, GameEventBus.Instance를 직접 참조 → 싱글톤 남용, 테스트 불가, 교체 어려움
적용 후 : ServiceLocator.Get<IAudioService>().Play("npc_idle") 처럼 인터페이스로만 접근 → NPC 코드는 구현 세부사항을 전혀 모름
나중에 사운드 시스템을 DummyAudioService로 교체해서 테스트할 때도, 등록만 바꾸면 NPC 코드는 한 줄도 건드릴 필요가 없어요!
5. 옵저버 패턴과의 비교
둘 다 "결합도를 낮추는 패턴"이지만 역할이 달라요.
아래 다이어그램으로 한눈에 비교해볼게요.

6. 주의할 점 — 안티패턴이라는 오해
서비스 로케이터는 종종 안티패턴으로 불리기도 해요. 의존성이 숨겨져서 "이 클래스가 뭘 필요로 하는지" 겉으로 보이지 않기 때문입니다.
그래서 실제 프로젝트에서 쓸 때는 이런 점을 챙기면 좋아요.
- 인터페이스로만 등록 → 구현체를 직접 꺼내 쓰면 의존성 역전의 의미가 없어집니다.
- Null Object 패턴 병행 → 등록되지 않은 서비스 요청 시 예외 대신 무동작 구현체를 반환하면 안전합니다.
- 씬 전환 시 재등록 → Unity에서는 씬이 바뀌면 등록된 서비스가 파괴될 수 있으니 DontDestroyOnLoad나 Bootstrapper로 관리해야 합니다.
7. 마무리
서비스 로케이터 패턴은 단순히 "전역에서 뭔가를 꺼내 쓰는 패턴"처럼 보이지만, 올바르게 쓰면 의존성 역전 + 교체 가능성 + 테스트 용이성 세 가지를 동시에 챙길 수 있는 강력한 패턴입니다.
특히 SoundManager, EventBus, ObjectPool처럼 여러 시스템에서 공유되는 서비스가 많은 Unity 게임 프로젝트에서는, 이 패턴을 익혀두면 싱글톤 지옥에서 벗어날 수 있으니 꼭 한 번 써보시길 추천드립니다!
'유니티' 카테고리의 다른 글
| 최적화 - 유니티 최적화 시리즈 시작! (0) | 2026.05.08 |
|---|---|
| Unity 기능 리뷰 - Avatar Mask + Animator Layer (1) | 2026.04.16 |
| 디자인 패턴 - 옵저버 패턴(Observer Pattern) (0) | 2026.04.02 |
| [패치 리뷰]Unity 6.3 주요 패치 내용!! (0) | 2026.02.10 |
| Unity 기능 리뷰 -렌더 파이프라인 (0) | 2025.11.30 |
