[Unity] 의존성 주입

Jongmin Kim·2025년 7월 5일

Unity

목록 보기
14/19
post-thumbnail

개요

나는 바보다. 제네릭 싱글턴에 DontDestroyOnLoad를 사용하고 그 싱글턴 클래스끼리 엮어버리는 바람에 세상에서 제일 맛있는 스파게티를 만들어버렸다.

근데 문제는 스파게티가 맛있어보여서 그냥 사용했다는 점이다. 싱글턴이 양날의 검이라는 것은 알고 있었지만.. 너무 편한데 어떡해.

그래서 기존에 만들어뒀던 게임을 잡아서 싱글턴을 없애보려고 노력했다. 싱글턴을 대체할 수 있는 방법을 찾아보다 예전에 공부했던 의존성 주입이 정답이었다는 것을 금새 알았다.

내가 사용하던 싱글턴 매니저는 다음과 같았다.
1. 게임 매니저
2. 사운드 매니저
3. 오브젝트 풀 매니저
4. 플레이어 데이터 매니저
5. 유닛 데이터 매니저

등등 한 7-8개 되었던 것 같다. GPT놈은 저 7-8개 모두 일반적으로 사용되는 목적의 싱글턴이에요~ 이러던데 깜빡 속았다. 아는 만큼 보인다더니 정말이다.

게임 매니저, 사운드 매니저, 오브젝트 풀 매니저, 로딩 매니저를 제외한 모든 싱글턴 클래스를 의존성 주입을 통해 극복해보고자 했다.



의존성

OOP에서 의존성이란 한 객체가 매개변수, 리턴 값, 지역 변수 등으로 다른 객체를 참조하는 것을 의미한다.

의존성이 높다는 말은 여러 객체와 관계를 맺고 있다는 의미로 결합도가 높고 테스트가 어려우며 코드를 수정하기 어렵다.

그렇기에 이 의존성을 낮추기 위해 어떻게 객체를 참조해야 하는가?에 대한 길로 들어선다.
길로 들어서고 싶지 않으면 그냥 싱글턴 쓰던가 해라.



의존성 주입

앞서 의존성을 낮추기 위해 어떻게 객체를 참조해야 하는가?라는 질문에 대한 해답 중 하나다.

의존성 주입(DI)은 하나의 객체가 다른 객체의 의존성을 제공하는 방법이다.

즉, 어떻게 의존성을 전달할 것인지 고민해, 최종적으로 객체의 생성(의존성 전달)과 사용의 관심(서비스)을 분리하는 관심사 분리다.

이때 의존 대상을 외부 코드로 주입하여 클라이언트는 주입된 대상을 사용한다.

이는 클라이언트가 주입 대상에 대한 구성 방식, 사용 중인 구체 클래스를 알 필요가 없음을 의미한다. 여기에 아주 잘 어울리는 키워드가 바로 interface다. 이 인터페이스를 적극 활용한다.

  • 고수준 모듈: 추상 클래스, 인터페이스
  • 저수준 모듈: 구체 클래스

생성자 주입

생성자 주입은 생성자를 통해 의존 관계를 주입하는 방법이다.

생성자 호출 시점에 단 한 번만 호출되는 것이 보장되고, final 변수 등을 추가해 불면, 필수 의존 관계에 사용된다. 일반적으로 주입받을 객체의 고수준 모듈을 주입받는다.

public class UnitMovement
{
	private Unit m_unit;
    
    public UnitMovement(Unit unit)
    {
    	m_unit = unit;
    }
}

수정자 주입

수정자 주입은 Setter를 통해 의존 관계를 주입하는 방법이다.
선택적이거나 변경될 여지가 있는 의존 관계에서 주로 사용한다.

public class UnitMovement
{
	private Unit m_unit;
    
    public void Initialize(Unit unit)
    {
    	m_unit = unit;
    }
}

인터페이스 주입

인터페이스로 의존성을 주입하고 상속해서 사용해 의존성을 주입하는 방법이다.

public interface IMovement {...}

public class UnitMovement : IMovement {...}

public interface IActionInjector
{
	void InjectMovement(IMovement movement);
}

public class PlayerCtrl : IActionInjector
{
	private IMovement m_movement;
    
    public void InjectMovement(IMovement movement)
    {
    	m_movement = movement;
    }
    
    public void DoSomething()
    {
    	m_movement?.Initialize();
    }
}



DI Container

DI Container의존성 주입을 자동화해주는 도구로 객체 생성/주입을 돕는다.

의존성을 관리할 컨테이너를 두어 필요한 곳에 주입하기 때문에 의존성 관리에 신경쓰지 않고 비즈니스 로직에 집중할 수 있다. 하나의 의존성을 여러 클래스가 나눠 사용할 경우에도 적용하기 좋다.

기존에는 분산된 위치에서 직접 인스턴스를 생성하여 주입했다면, DI Container를 통해 컨테이너 속에 미리 사용할 모든 인스턴스를 생성하여 등록하고, 필요하면 주입하여 사용할 수 있다.


의존성 주입을 자동화한만큼 의존 관계가 느슨해져 객체 간 결합도가 줄어들고, 재사용이 편리하며, 테스트가 용이해진다.

하지만 의존성을 주입할 때마다 객체의 생성/파괴가 반복되고, 외부에서 주입해 관계가 느슨해진만큼 코드 추적이 어렵다.

의존성이 분리된 만큼 추가 클래스들이 생길 수 있고 구조에 대한 고민이 필요하다.


유니티에서도 DI Container 기능을 제공하는 프레임워크가 있다는데, Unity Container, Zenject, VContainer 등이 있는 것 같다.

Zenject가 제일 유명한 듯 했으나 프로젝트가 중지된 것 같고, Unity Container도 정보가 그다지 많은 편은 아닌거 같다.



결론

DI Container를 사용해보고는 싶지만, 프레임워크를 또 공부해야 한다니.. 뭔가 도움은 될거 같지만 배보다 배꼽이 더 큰거 같기도 하다. 실무에선 이 프레임워크를 안쓸지도 모르는 일이고 말이다.

사실 유니티에서는 강력한 DI 도구인 SerializeField를 제공하니 이를 적극 활용해봐야겠다.

이론적인 내용 자체는 매우 간단하고 쉽지만.. 막상 적용하려고 하니 코드 추적이 어렵다는게 진짜 큰 것 같다. 이것이 다 싱글턴에 익숙해진 내 잘못이라고 생각한다.

profile
Game Client Programmer

1개의 댓글

comment-user-thumbnail
2025년 7월 8일

아가콩이 너무 잘해또여 ! 🍼

답글 달기