의존성 주입(DI)란?
클래스는 다른 클래스의 참조가 필요하다. 이때, 필요한 클래스를 의존성이라고 하며, 의존성의 인스턴스가 요구된다.
클래스가 필요한 객체를 얻는 방법에는 3가지가 있다.
-
클래스가 필요한 의존성을 생성하여 초기화한다.
class Car {
private val engine = Engine()
fun start() {
engine.start()
}
}
fun main(args: Array) {
val car = Car()
car.start()
}yy6
단점
- Car와 Engine이 강하게 연결되어 있다. → 한가지 유형의 Engine을 사용하므로 서브 클래스나 대체 구현을 쉽게 사용할 수 없다.
- 강한 의존성으로 테스트를 어렵게 만든다. → 실제 Engine 인스턴스를 사용하므로 테스트 더블을 사용하여 Engine을 대체할 수 없다.
-
다른 곳에서 객체를 가져온다.
( ex, Context getter, getSystemService() 와 같은 일부 안드로이드 API)
-
객체를 매개변수(클래스 구성시 or 필요한 함수)로 제공 받는다. → 의존성 주입
class Car(private val engine: Engine) {
fun start() {
engine.start()
}
}
fun main(args: Array) {
val engine = Engine()
val car = Car(engine)
car.start()
}
장점
- 다양한 Engine 구현을 Car에 전달할 수 있다. → Car의 추가 변경이 필요 없다.
- 테스트 더블을 전달하여 다양한 시나리오를 테스트 할 수 있다.

- DI는 일반 코드가 특정 코드의 실행을 제어하는 제어 반전 원칙을 기반으로 한다.
DI의 이점
- 클래스 재사용 가능 및 의존성 분리
⇒ 제어 반전으로 인해 코드 재사용 개선
- 리팩터링 편의성 ⬆
의존성은 API에 노출된 영역에 있으므로, 구현 세부정보로 숨겨지지 않고 객체 생성 타임 또는 컴파일 타임에 확인할 수 있습니다.
- 테스트 편의성 ⬆
클래스는 의존성을 관리하지 않으므로 테스트 시 다양한 구현을 전달하여 다양한 모든 사례를 테스트할 수 있습니다.
DI 방법
- 생성자 삽입 : 클래스의 의존성을 생성자에 전달
- 필드 삽입(or setter 삽입) : Activity, Fragment와 같이 시스템에서 인스턴스화를 하는 경우 생성자 삽입이 불가능하다. 필드 삽입을 사용하면 의존성은 클래스가 생성된 후 인스턴스화된다.
수동 DI
위의 예제들처럼 의존성을 직접 생성, 제공 및 관리하는 방식을 직접 의존성 주입 또는 수동 의존성 주입이라고 한다.
- 대규모 어플의 경우 많은 보일러 플레이트 코드가 생긴다.
- 지연 초기화와 같이 의존성을 전달하기 전에 생성할 수 없을 때, 의존성의 수명주기를 관리하는 커스텀 컨테이너를 구현하여야 한다.