DI(Dependency Injection)

  • 구성 요소 간의 의존 관계가 소스 코드 내부가 아닌 외부 설정 파일 등을 통해 정의되게 하는 디자인 패턴.
  • 분리시켜 놓음으로써 객체의 생성과 사용을 분리시킬 수 있고, 재사용이 유연해진다.

  • 크게 3가지 유형으로 나누어 볼 수 있다.

    1. 생성자 주입 : 필요한 의존성을 모두 포함하는 클래스의 생성자를 만들고 그 생성자를 통해 의존성을 주입한다.(가장 권장된다.)
    2. Setter를 통한 주입 : 의존성을 입력 받는 Setter 메소드를 만들고 이를 통해 의존성을 주입한다.
    3. Interface를 통한 주입 : 의존성을 주입하는 함수를 포함한 인터페이스를 작성하고 이 인터페이스를 구현하도록 함으로써 실행 시에 이를 통해 의존성을 주입한다.
  • 주의할 점 : 생성자 주입이 권장되는 방법이지만, Activity 같은 경우에는 멤버 인젝션을 해야 한다. 이유는 Activity에서 생성자를 쓰지 않기 때문이다. 물론 할 수도 있지만, onCreate()보다 먼저 실행되어버리기 때문에 절대로 권장하지 않는다.

  • 장점

    • 클래스 간의 결합도를 낮춰 유지 보수를 용이하게 할 수 있다.
    • 코드 재사용을 높여서 작성된 모듈을 여러 곳에서 소스 코드의 수정 없이 사용할 수 있다.
    • Mock 객체 등을 이용한 단위 테스트의 편의성을 높여준다.

위의 그림에서 A 클래스가 B 클래스를 의존할 때, B의 Object를 A가 직접 생성하지 않고 외부에서 생성하여 넘겨주면 의존성을 주입했다고 할 수 있다.

결국, DI를 위해서는 객체를 생성하고 넘겨주는 외부의 무언가가 필요하다. 직접 DI를 제공하는 Provide 형태의 클래스를 만들 수 있고, 라이브러리로 넘겨서 대신 처리하도록 구현할 수도 있다.

DI는 이렇게 의존성이 있는 객체의 제어를 외부 Framework로 올리면서 IoC(Inversion of Control) 개념을 구현한다. -> 제 3자에 의해 정의 당하기 때문에 의존 관계가 역전되었다고 이해할 수 있다.

이를 지원하는 라이브러리 중 Dagger2와 koin이 있으며, 저번에는 koin에 대해 알아봤고 이번에는 Dagger2에 대해 알아볼 예정이다.

DI의 필요성?

  1. 의존성 파라미터를 생성자에 작성하지 않아도 되므로 보일러 플레이트 코드를 많이 줄일 수 있다. 보일러 플레이트 코드를 줄이는 것만으로도 유연한 프로그래밍이 가능.
  2. Interface에 구현체를 쉽게 교체하면서 상황에 따라 적절한 행동을 정의할 수 있다. Mock 객체와 실제 객체를 바꿔가며 테스트할 때, 유용.

Dagger2의 핵심 키워드

  • Inject
    의존성 주입을 요청한다. Inject 어노테이션으로 주입을 요청하면 연결된 Component가 Module로부터 객체를 생성하여 Inject를 요청한 쪽으로 넘겨준다.

  • Component
    연결된 Module을 이용해 의존성 객체를 생성하고, Inject로 요청받은 인스턴스에 생성한 객체를 주입한다.

의존성을 요청받고 주입하는 Dagger의 주된 역할을 수행한다.

  • Module
    Component에 연결되어 의존성 객체를 생성하여 제공한다. 생성 후 Scope에 따라 관리도 한다.

  • Scope
    생성된 객체의 Lifecycle 범위를 뜻한다. 안드로이드에서는 주로 PerActivity, PerFragment 등으로 화면의 생명주기와 맞추어 사용한다. Module에서 Scope을 보고 객체를 관리한다.

  • Subcomponent
    Component는 계층 관계를 만들 수 있다. Subcomponent는 Inner Class 방식의 하위 계층 Component이다. Sub의 Sub도 가능하다.

Subcomponent는 Dagger의 중요한 컨셉인 그래프를 형성한다. Inject로 주입을 요청받으면 Subcomponent에서 먼저 의존성을 검색하고 없으면 부모로 올라가면서 검색한다.

@Module + @Provides -> 의존성 제공
@Inject -> 의존성 요청
@Component -> Module과 Inject 사이의 브릿지 역할 수행
@Qualifier -> 동이한 유형이지만, 다른 인스턴스를 가진 객체를 구별하는 데 사용된다.

Flow
@Inject -> Subcomponent -> Modulce -> Scope에 있으면 return. 없으면 생성.
Subcomponent Module에서 맞는 타입을 못찾으면 상위 Component -> Module -> Scope에 있으면 return. 없으면 생성.

그림으로 한번 더 이해하기

  • Module, Provides -> 공급자의 역할.
    • Module : 의존성을 제공하는 @Provides 메소드를 가진 클래스에 이용한다. 모든 @Provides 메소드는 @Module 클래스 안에 속해야 한다.
    • Provides : 어떻게 의존성을 구성하고 제공하는지 정의하는 메소드에 사용한다.
  • Inject -> 소비자.
  • Component -> 위의 둘을 연결해주는 역할을 한다.