[TIL] DI와 DI 컨테이너
개요
우리가 흔히 싱글톤으로 관리하는 코드는 편리하지만 몇가지 문제가 있다
- 숨은 종속성: 필드, 시그니처만 봐선 의존이 안보이고 .Instance가 본문 깊은 곳에 숨음
- 스트 곤란: 전역 정적 상태라 로직만 검증하고 시어도 진짜 매니저가 필요
- 높은 결합도: 구체 클래스에 직접 묶여 구현 교체, 다른 프로젝트 재사용이 어려움
"하나뿐"은 살리고 전역 접근만 걷어내고자 DI와 DI컨테이너 사용
싱글턴 분석
csharp
// 싱글턴 수업에서 배운 고전 형태
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; } // ① 전역 정적 접근
private void Awake()
{
// 같은 매니저를 담은 씬을 다시 로드하면 두 번째가 생긴다 → 이 가드로 지움(안티패턴의 증상)
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject); // ② 씬을 넘어 생존
}
}
| 묶인 특성 | 무엇인가 | DI는 무엇으로 대체하나 |
|---|---|---|
① 전역 정적 접근(.Instance) | 어디서나 끌어다 쓰는 접근/가시성 | 주입: 의존을 시그니처로 받음(3·5절) |
② 씬 생존(DontDestroyOnLoad) | 씬을 넘어 사는 수명/스코프 | Root LifetimeScope의 Singleton 수명(3차시) |
핵심은 필요한 의존을 전역에서 꺼내(pull)쓰지 않고, 어떻게 밖에서 건네받게(push)할까?
DI의 정의
Dependency Injection: 가져오기(pull)을 건네받기(push)로 뒤집는 것
객체가 필요한 의존을 스스로 찾지 않고 밖에서 건네 받음(주입)
new, .Instance, FindObjectOfType, GameObject.Find, GetComponent로 씬을 뒤져 캐싱 = pull
[SerializeField] 인스펙터 드래그 = push
DIP(원칙)와 DI(기법)
DIP는 SOLID에 종속성 역전 원칙 "구체가 아니라 추상화에 의존하라"
DI는 그 원칙을 코드로 실현하는 기법 "어느 방향으로 의존해야 하는가"
순수 DI와 조립 지점
- 1단계: 추상화 추출
- 2단계: 의존을 주입받기
- 3단계: 조립 지점에서 손 조립
한계
- 조립 코드 비대화
- 수명 관리 수동
- 실행 루프 연결 수동
- 씬 경계 전달 번거로움
DI 컨테이너
조립 지점의 수작업을 자동화하는 라이브러리. 어떤 컨테이너든 핵심은 다음 세 동작
| 동작 | 답하는 질문 | 컨테이너가 하는 일 |
|---|---|---|
| 등록(Register) | 무엇을, 어떤 수명으로? | “IAudioService가 필요하면 AudioManager를 줘라” 같은 조립 규칙을 한곳에 모아 둠 |
| 해석(Resolve) | 이 타입 하나 줘 | 생성자 시그니처를 분석해 필요한 의존을 재귀적으로 만들어 끼움, 그래프 자동 조립 |
| 수명(Lifetime) | 몇 개나, 언제까지? | 하나를 공유할지 매번 새로 만들지 결정하고, 스코프가 끝나면 Dispose까지 관리 |



![[Unity]ShaderGraph로 2D 흙타일 표현하기](https://data.develog.develrocket.com/upload/develog/ghks5658/1786458222815-ld97jd/_____2026-08-11_222113.png)


