[Android] 의존성 주입 (Dependency-Injection)

민채·2024년 3월 5일
0

Android

목록 보기
13/16

의존성 주입 (DI)

  • 프로그래밍에서 구성요소간의 의존 관계가 내부가 아닌 외부를 통해 정의되게 하는 디자인 패턴 중의 하나
  • 의존성 주입의 목적은 객체를 생성하고 사용하는 관심사를 분리하는 것
  • 클래스와 클래스간에 관계를 맺을 때, 내부에서 직접 생성하는 것이 아닌, 외부에서 주입을 함으로써 관계를 맺게 만드는 것을 의미

장점

  • 코드의 재사용 및 의존성 분리 -> 클래스 간의 결합도를 줄임
  • 리팩토링이 수월함
  • 테스트 편의성 -> 클래스가 의존성을 관리하지 않으므로 테스트를 더 쉽게 수행할 수 있음

단점

  • 의존성 주입을 위한 선행작업이 필요하고 코드를 추적하고 읽기가 어려워짐

Dagger2

  • 안드로이드 앱에서 의존성 주입을 구현하는 데 사용되는 서드파티 라이브러리

Module, Provide

  • Component에 연결되어 의존성 객체를 생성, 생성 후 Scope에 따라 객체를 관리함
  • @Module은 클래스에만 붙이고, @Provides는 반드시 @Module 클래스에 선언된 메서드에만 붙임
  • Module클래스는 의존성 주입에 필요한 객체들을 Provide메소드를 통해 관리
  • Provide 메소드의 파라미터도 컴포넌트 구현체로부터 전달 받을 수 있음

Inject

  • 필드, 생성자, 메서드에 붙여 Component로 부터 의존성 객체를 주입 요청하는 annotation
  • 객체를 주입받기 때문에 객체의 생성이 클래스에 의존적이지 않고 보일러플레이트코드를 작성할 필요없이 클래스를 테스트하기가 수월해짐
  • 인터페이스는 생성자가 없으므로 @Inject를 붙일 수 없음

Component

  • Interface 또는 abstract class에만 사용이 가능
  • 연결된 Module을 이용하여 의존성 객체를 생성하고, Inject로 요청받은 인스턴스에 생성한 객체를 전달(주입)
  • 의존성을 요청받고 전달하는 Dagger의 주된 역할을 수행

SubComponent

  • 부모 Component가 있는 자식 Component
  • Component와 달리 코드 생성은 부모 Component에서 이루어짐
  • Component와 마찬가지로 interface 또는 abstract class에서 사용

Scope

  • Component별로 @Scope annotation으로 주입되는 객체들을 관리
  • 생성된 객체의 Lifecycle 범위

참조

Hilt

  • Dagger를 기반으로 만든 DI 라이브러리
  • 프로젝트의 모든 Android 클래스에 컨테이너를 제공하고 수명 주기를 자동으로 관리함으로써 애플리케이션에서 DI를 사용하는 표준 방법을 제공
  • Dagger2보다 표준화된 사용법을 제시하고 보일러 플레이트 코드를 감소 시킴

참조

Koin

  • 안드로이드에서 사용할 수 있는 코틀린 DI 라이브러리

장점

  • 러닝커브가 낮아 쉽고 빠르게 DI를 적용할 수 있음
  • Kotlin 개발 환경에 도입하기 쉬움
  • 별도의 어노테이션을 사용하지 않기 때문에 컴파일 시간이 단축
  • ViewModel 주입을 쉽게 할 수 있는 별도의 라이브러리를 제공

사용법

build.gradle 설정

dependencies {
    // Koin
    implementation("io.insert-koin:koin-core:3.1.2")
    implementation("io.insert-koin:koin-android:3.1.2")
}

샘플 클래스 생성

class SampleRepository() {
    val sampleData = "Sample Data!"
}

class SampleController(val repository: SampleRepository) {
    fun printSampleData() {
        Log.d("Print sample data", repository.sampleData)
    }
}

class SampleViewModel : ViewModel() {
    private var _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> = _isLoading
}

모듈 선언

  • module로 주입 받고자 하는 객체를 모듈로 만들어 선언
val appModule = module {
    single { SampleRepository() }
    factory { SampleController(get()) }
}

val viewModelModule = module {
    viewModel { SampleViewModel() }
}
  • single : 컨테이너 내에서 단 한번만 생성됨
  • factory : 컨테이너 내에서 요청 시점마다 새로운 객체를 생성
  • get() : 컨테이너 내에서 알맞은 의존성을 주입 (위 코드에서는 SampleRepository가 주입됨)
  • viewModel : 컨테이너 내에 해당 viewModel을 등록(ViewModelFactory 안에 Koin이 알아서 등록해줌)

컨테이너 생성 (모듈 등록)

  • Application 클래스의 onCreate LifeCycle에서 startKoin을 호출하고 위에서 선언한 모듈 변수를 넘겨줌
  • Application의 생명주기에 맞게 생성되고 삭제됨
class SampleApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidLogger()
            androidContext(this@SampleApplication)
            modules(appModule)
            modules(viewModelModule)
        }
    }
}

Kodein

profile
코딩계의 떠오르는 태양☀️

0개의 댓글