티스토리 뷰

반응형

1. DI의 개념

DI란 Dependency Injection의 약자로 의존성 주입을 의미한다. 의존성 주입은 하나의 객체가 다른 객체의 의존성을 제공하는 기술이다. 비유하자면 '의존성'은 서비스로 사용할 수 있는 객체이고 '주입'은 의존성(서비스)을 사용하려는 객체로 전달하는 것을 의미한다. DI는 프로그래밍에 널리 사용되는 기법으로, DI의 원칙을 따르면 훌륭한 앱 아키텍처를 위한 토대를 마련할 수 있다.

 

DI를 클래스들로 예를 들어 설명하자면, Car 클래스와 Engine 클래스가 있는 경우 Car 클래스가 실행되기 위해서는 Engine 클래스의 인스턴스가 있어야 한다. 이러한 필요한 클래스(Engine)를 종속 항목(=의존성)이라고 한다. 클래스들은 흔히 다른 클래스 객체가 필요하다(의존적이다). 클래스가 필요한 객체를 얻는 방법은 다음 세 가지 방법이 있다.

 

1) 클래스가 필요한 종속 항목을 구성한다.

위 예에 적용하면 Car는 자체 Engine 인스턴스를 생성하여 초기화한다.

class Car {

    private val engine = Engine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}

 

클래스들의 관계를 그림으로 나타내면 다음과 같다.

이미지 출처: 안드로이드 개발자 공식 문서

 

이러한 방법으로 클래스가 필요한 객체를 얻는 경우 Car와 Engine이 밀접하게 연결되어 있기 때문에 문제가 생길 수 있다. 우선, 코드의 재사용이 어려워진다. Car 인스턴스는 자신이 생성한 한 가지 유형의 Engine만을 사용하기 때문에 Engine 유형이 Gas와 Electric 두 가지라면 하나의 Car 인스턴스를 재사용하는 대신 두 가지 유형의 Car를 생성해야 한다. 또한 이처럼 종속 항목이 강하게 의존하고 있는 경우 다양한 테스트를 하기 어려워진다. Car가 실제 Engine 인스턴스를 사용하기 때문에 다양한 테스트 상황에서 Engine을 수정할 수 없게 된다.

 

2) 다른 곳에서 객체를 가져온다.

Context getter 혹은 getSystemService()와 같은 일부 안드로이드 API가 이러한 방식으로 작동한다.

 

3) 객체를 매개변수로 전달받는다.

앱은 클래스가 구성될 때 종속 항목을 제공하거나 각 종속 항목이 필요한 함수에 전달할 수 있다. 위 예에 적용하면 Car 인스턴스는 초기화 시 자체 Engine 객체를 구성하는 대신 Engine 객체를 생성자의 매개변수로 받는다.

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 인스턴스를 구성하기 때문에 다양한 유형의 Engine을 Car에 전달해 Car의 재사용이 가능해지며 다양한 시나리오를 테스트할 수 있다.

 

위의 세 가지 방법 중 이 세 번째 방법이 바로 '의존성 주입(DI)'이다.

 

2. DI의 장점

위에서 예시를 살펴보면서 설명한 것처럼 DI를 구현하면 다음과 같은 장점이 있다.

 

  • 코드 재사용 가능: 종속 항목 객체를 쉽게 교체할 수 있다.
  • 리팩토링 편의성: 종속 항목은 구현 세부정보로 숨겨지지 않고 노출되어 있어 검증 가능한 요소가 되므로 객체 생성 또는 컴파일 시 확인할 수 있다.
  • 테스트 편의성: 클래스가 종속 항목을 관리하지 않으므로 다양한 모든 사례를 테스트할 수 있다.

 

3. 안드로이드에서 DI(의존성 주입) 실행하기

안드로이드에서 의존성 주입을 실행하는 두 가지 주요 방법은 다음과 같다.

 

  • 생성자 삽입: 위에서 설명한 방법으로, 클래스의 종속 항목을 생성자에 전달하는 방법이다.
  • 필트 삽입(setter 삽입): Activity나 Fragment 같은 특정 안드로이드 프레임워크 클래스는 시스템에서 인스턴스화하기 때문에 생성자 삽입이 불가능하다. 따라서 필드 삽입을 사용해 클래스가 생성된 후 종속 항목을 인스턴스화한다. 코드는 다음과 같다.
class Car {
    lateinit var engine: Engine

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.engine = Engine()
    car.start()
}

 

4. 안드로이드의 DI 라이브러리

위의 예에서처럼 클래스의 종속 항목을 직접 생성, 제공, 관리하는 것을 수동 의존성 주입이라고 한다. 위의 예에서 Car에 종속 항목이 하나만 있었기 때문에 수동으로 의존성을 주입하는 것이 가능했지만 종속 항목과 클래스가 많아지면 수동 의존성 주입을 사용할 때 문제가 발생할 수 있다. 따라서 종속 항목을 생성하고 제공하는 프로세스를 자동화해서 문제를 해결하는 라이브러리를 사용하는 것이 좋다. 안드로이드의 주요 DI 라이브러리는 다음과 같다.

 

  • Hilt
  • Dagger

Dagger는 구글에서 유지 관리하며 자바, 코틀린 및 안드로이드용으로 널리 사용되는 라이브러리이다. 컴파일 타임 정확성, 런타임 성능, 확장성 및 안드로이드 스튜디오 지원 등의 장점이 있다. Hilt는 Dagger를 기반으로 빌드된 Jetpack의 권장 라이브러리로, 프로젝트의 모든 안드로이드 클래스에 컨테이너를 제공하고 생명주기를 자동으로 관리해 앱에서 DI를 실행하는 표준 방법을 정의한다.

 

참고

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함