Zenject 의 조회는 가까운 컨테이너와 조건 있는 바인딩을 고른다
Esc

    Zenject 의 조회는 가까운 컨테이너와 조건 있는 바인딩을 고른다

    –Unity라이브러리DI
    // ProjectContext 설치 파일
    Container.Bind<IWeapon>().To<Bow>().AsSingle();
    // 씬 설치 파일
    Container.Bind<IWeapon>().To<Sword>().AsSingle();
    
    public class Archer { [Inject] IWeapon _weapon; }   // 무엇이 들어올까

    같은 계약이 두 곳에 있어도 설치는 조용히 지나갑니다. 그러면 Archer 는 Sword 를 받을까, Bow 를 받을까, 예외를 받을까요.

    Zenject 의 단일 조회는 가까운 컨테이너가 이기고, 같은 거리에서는 조건 있는 바인딩이 이기고, 같은 등급이 둘이면 처음 조회할 때 예외입니다. 여러 개를 달라고 하면 이 규칙을 쓰지 않고 전부 모읍니다.

    가까운 컨테이너가 이기고, 같은 거리에서는 조건 있는 쪽이 이긴다

    조회 키는 (계약 타입, WithId 값) 쌍인 BindingId 입니다. WithId("main") 으로 바인딩한 것은 [Inject(Id = "main")] 으로만 찾습니다. 키로 찾은 후보는 이렇게 고릅니다.

    • 자기 컨테이너가 거리 0, 부모가 1, 조부모가 2 입니다. 가까운 데서 고르면 먼 컨테이너는 보지 않습니다.
    • 같은 거리에서는 When 조건을 통과한 후보가 조건 없는 후보를 이깁니다.
    • 같은 거리에 같은 등급이 둘이면 모호하다며 예외를 던집니다.

    맨 위 예제의 Archer 는 Sword 를 받습니다. 부모의 Bow 는 예외도 경고도 없이 가려집니다. 모호성 예외는 등록할 때가 아니라 처음 조회할 때 납니다. 등록은 목록에 하나를 더 붙이는 일이라 막을 이유가 없고, 몇 개가 경쟁하는지는 조회해 봐야 압니다.

    찾을 범위는 [Inject(Source = ...)] 로 바꿉니다. 기본값 Any 는 자기와 모든 조상, Local 은 자기만, Parent 는 직속 부모만, AnyParent 는 조상만입니다.

    여러 개를 달라고 하면 거리도 모호성도 따지지 않는다

    ResolveAll 은 범위 안 모든 컨테이너의 후보를 이어 붙이고, 조건만 거릅니다. 배열, List<T>, IEnumerable<T> 같은 목록 요청은 그 타입의 바인딩이 없으면 원소 타입의 ResolveAll 로 넘어가는데, 이때 요청을 선택(Optional)으로 바꿉니다.

    그래서 같은 계약을 단일로 받는 쪽은 하나를, 목록으로 받는 쪽은 둘을 봅니다. 단일 요청이 아무것도 못 찾으면 선택 요청은 기본값을 받고, 나머지는 Unable to resolve 예외입니다. 목록만 예외 없이 빈 목록으로 통과합니다.

    Zenject 도 이 범위를 적어 씁니다. 진입점을 부르는 InitializableManager 는 [Inject(Optional = true, Source = InjectSources.Local)] List<IInitializable> 로 받아, 부모 컨테이너의 진입점을 섞지 않습니다.

    주의할 점

    1. 같은 계약을 조건 없이 두 번 바인딩하지 않는다

    안 지키면 생기는 문제. 같은 컨테이너에 IWeapon 을 두 번 바인딩해도 설치는 조용히 지나가고, 예외는 누가 처음 IWeapon 하나를 요구할 때 Found multiple matches when only one was expected 로 납니다. 드문 경로의 조회라면 한참 뒤에 드러나고, 두 설치 파일이 모르고 같은 계약을 넣은 경우라면 어느 쪽인지 바로 보이지 않습니다.

    이렇게 쓴다. 여러 구현이 필요하면 쓰는 쪽을 목록으로 받거나, WithId 로 이름을 가르거나, When 계열 조건으로 누가 무엇을 받는지 정합니다. 실행 전 검증을 돌리면 이 예외도 플레이 전에 나옵니다.

    2. 자식 컨테이너의 바인딩은 부모 것을 가린다

    안 지키면 생기는 문제. 프로젝트 컨테이너의 계약을 씬 설치 파일이 다시 바인딩하면 그 씬의 단일 조회는 모두 씬 것을 받고, 경고는 없습니다. 반면 ResolveAll 과 목록 주입은 부모 것까지 받아, 한쪽은 하나, 한쪽은 둘을 봅니다.

    이렇게 쓴다. 전역 서비스는 프로젝트 컨테이너 한 곳에만 바인딩합니다. 씬마다 바꾸는 것이 의도라면 그 계약은 프로젝트 쪽에 두지 않습니다. 목록 범위를 좁혀야 하면 [Inject(Source = InjectSources.Local)] 을 적습니다.

    3. 목록 주입은 비어 있어도 통과한다

    안 지키면 생기는 문제. 목록 요청은 선택으로 바뀌므로 원소 바인딩이 하나도 없어도 빈 목록을 받고 검증도 통과합니다. 설치 파일에서 한 줄을 지운 실수가 “아무 일도 안 일어나는 기능” 으로만 드러납니다.

    이렇게 쓴다. 적어도 하나는 있어야 하는 목록이면 생성자에서 개수를 검사해 바로 예외를 던집니다. 단일로 받을 수 있는 자리라면 단일로 받아, 빠졌을 때 Unable to resolve 가 나게 합니다.

    정리

    Zenject 의 단일 조회는 가까운 컨테이너, 조건 있는 바인딩 순으로 하나를 고르고, 같은 등급이 둘이면 처음 조회할 때 예외입니다.

    • 조회 키는 (계약 타입, Id) 쌍이고, 범위는 Source 로 Any, Local, Parent, AnyParent 중에서 고릅니다.
    • 자식의 바인딩은 부모 것을 경고 없이 가리고, 모호성은 등록할 때 막지 않습니다.
    • ResolveAll 과 목록 주입은 모든 컨테이너의 후보를 모으고, 목록은 비어 있어도 예외 없이 통과합니다.