오브젝트 컨테이너(Object Container)는 런타임에 생성·소멸·관리되는 다수의 객체를 하나의 논리적 공간에 모아 생명주기와 의존성을 통제하는 구조적 장치이다. 흔히 “DI 컨테이너(Dependency Injection Container)”·“서비스 로케이터”·“오브젝트 풀”이 같은 범주에 언급되나, 본 글에서는 의존성 주입을 중심으로 한 컨테이너를 다룬다.
1. 필요성
- 결합도 감소 : new를 직접 호출하지 않고 컨테이너가 객체를 주입하므로 클래스 간 직접 의존이 사라진다.
- 테스트 용이성 : 모킹(Mock)을 등록해 교체하면 단위 테스트가 수월해진다.
- 수명 주기 일원화 : Singleton, Scoped, Transient 같은 정책을 메타데이터로 선언해 메모리·성능을 예측 가능하게 만든다.
- 설정 기반 아키텍처 : 런타임에 ScriptableObject나 JSON으로 등록 목록을 교체해 빌드 없이 기능을 확장한다.
2. 동작 원리 요약
- 등록(Registration)
- 타입 혹은 키 → 구체 구현체·팩터리·프리팹 등을 매핑
- 라이프사이클(Scope) 정보 포함
- 해결(Resolution)
- 필요 객체가 요청되면 컨테이너가 그래프를 탐색하며 생성·캐싱
- 주기적 정리(Teardown)
- 씬 언로드나 플레이 모드 종료 시 Dispose 및 풀 반환 수행
3. Unity 환경에서의 구현 예시
아래 코드는 최소화된 마이크로 DI 컨테이너로, Editor 없이도 동작하도록 ScriptableObject로 등록 목록을 보관한다.
public enum Lifetime { Singleton, Transient }
[Serializable]
public sealed class Registration
{
public string key;
public MonoBehaviour prefab;
public Lifetime lifetime = Lifetime.Transient;
private MonoBehaviour singletonInstance;
public MonoBehaviour Resolve(Transform parent = null)
{
if (lifetime == Lifetime.Singleton && singletonInstance != null)
return singletonInstance;
var inst = Object.Instantiate(prefab, parent);
if (lifetime == Lifetime.Singleton) singletonInstance = inst;
return inst;
}
}
[CreateAssetMenu(menuName = "DI/ObjectContainer")]
public sealed class ObjectContainer : ScriptableObject
{
[SerializeField] private List<Registration> registrations = new();
public T Resolve<T>(string key, Transform parent = null) where T : MonoBehaviour
{
var reg = registrations.Find(r => r.key == key);
if (reg == null) throw new Exception($"Registration not found: {key}");
return (T)reg.Resolve(parent);
}
}
사용법
- 컨테이너 SO를 프로젝트에 생성 후 Inspector에서 프리팹과 키·수명 주기를 등록
- 런타임 초기화 시 Resources.Load<ObjectContainer>("MainContainer")로 호출
- 스크립트 내에서 container.Resolve<EnemyController>("Goblin")과 같이 요청
4. 설계 시 주의점
- 순환 의존성 : A→B, B→A 구조는 컨테이너가 객체 그래프를 완성하지 못해 예외를 유발한다. 생성자 대신 팩터리 메서드 패턴으로 우회하거나, Lazy<T> 참조로 지연 해결한다.
- 퍼포먼스 : 모바일·VR에서는 리플렉션 기반 자동 주입이 과도한 GC를 초래할 수 있다. 컴파일 타임 바인딩(예 : Source Generator)이나 사전 생성된 팩터리 클래스로 비용을 상수화한다.
- 씬 전환 : DontDestroyOnLoad 단일 컨테이너는 편하지만, 씬별 의존성 격리가 어려워 메모리 누수가 발생한다. 루트 컨테이너 + 씬 범위 하위 컨테이너 2단 계 구성이 권장된다.
5. Rx·FSM·오브젝트 풀과의 연계(후반부 권장 사항)
컨테이너는 RxVar·RxStateMachine을 포함한 각종 매니저를 싱글턴으로 주입함으로써, 이벤트·상태·의존성 흐름을 한곳에서 추적 가능하게 만든다. 또한 풀링 매니저를 Transient 대신 Singleton으로 등록해 대량 객체 재사용을 자동화하면, 실시간 게임에서도 스파이크 없는 안정적인 프레임을 유지할 수 있다.
맺음말
오브젝트 컨테이너는 **“객체 생성 책임의 중앙 집중화”**를 통해 대규모 코드베이스의 복잡성을 통제하는 핵심 수단이다. Unity에서도 Zenject·VContainer 등을 통해 이미 검증된 패턴이니, 초기 설계 단계에서 컨테이너 도입 여부와 범위를 결정하면 후속 리팩터링 비용을 크게 줄일 수 있다.
'소프트웨어 공학 > 디자인 패턴' 카테고리의 다른 글
| 연관 기반 디자인 vs 소유 기반 디자인 (0) | 2025.06.02 |
|---|---|
| 팩토리 패턴(Factory Pattern) (0) | 2025.05.08 |