DI (Dependency Injection) 패턴
- 컴포넌트간의 의존 관계를 소스코드 내부가 아닌 외부 설정 파일등을 통해 정의되게하는 디자인 패턴중 하나
- 객체를 직접 생성하지 않고 외부에서 주입한 객체를 사용하는 방식
- 인스턴스 간 디커플링을 만들어줌 => 유닛테스트 용이성 증대
( 객체A 내부에서 객체B 를 사용하는 경우 객체B에 대해 객체A가 의존하고 있다고 볼 수 있으며,
객체A에서 객체B를 초기화하지 않고 사용할 수 있도록 외부에서 객체B를 초기화하여 객체A에 주입해주는 것 )
Ex) Hilt, Dagger
Service Locator 패턴
- 중앙 등록자 ‘Service Locator’를 통해 요청이 들어왔을 때 특정 인스턴스 반환
- apk 크기, 빌드 속도, 메서드 수 등 복잡한 제약이 있는 경우 사용하기 편함
Ex) Koin(경량화 된 DI라고 소개하지만, 내부 동작은 Service Locator로 봐도 무방)
🚨 Dependency Injection vs Service Locator
Dependency Injection | Service Locator | |
종속성 | 일부 핵심 클래스에 종속성을 주입 | 모든 클래스가 서비스 로케이터에 종속 |
호출방법 | 처음 한번만 호출 (명시적인 호출이 없음) | 인젝터를 직접 호출 (명시적인 호출) |
의존관계 | 의존 관계 파악이 쉬움 | 의존 관계 파악이 어려움 |
코인 Koin
순수 kotlin 으로 작성된 경량화된 Dependency Injection 프레임워크로, 내부 동작은 Service Locator 형태를 띈다.
🚨 Koin 장단점
* 장점
- 러닝커브가 낮아 쉽고 빠르게 DI를 적용할 수 있다.
- Kotlin 개발 환경에 도입하기 쉽다.
- 별도의 어노테이션을 사용하지 않기 때문에 컴파일 시간이 단축된다.
- ViewModel 주입을 쉽게 할 수 있는 별도의 라이브러리를 제공한다.
* 단점
- 런타임시 주입이 필요한 컴포넌트가 생성이 되어있지 않는
파라미터가 있는경우 크래시가 발생할 수 있다.
- 컴파일 타임에 주입대상을 선정하는 DI에 비해 런타임에 서비스 로케이팅을
통해 인스턴스를 동적으로 주입해주기 떄문에 런타임 퍼포먼스가 떨어질 수 있다.
🚨 Koin 사용 방법
module{ ... } : 키워드로 주입받고자 하는 객체의 집합
single{ ... } : 앱이 실행되는 동안 계속 유지되는 싱글톤 객체를 생성, 싱글톤scope 로 해당 객체를 만들어서 사용
factory{ ... } : 요청할 때마다 매번 새로운 객체를 생성
get() : 컴포넌트 내에서 알맞은 의존성을 주입
build.gradle(:app)
하단 dependencies {} 안에 koin 에 대한 라이브러리를 사용하기 위해 추가하여 사용한다.
plugins { ... }
android { ... }
dependencies {
...
//koin
implementation "org.koin:koin-android:2.1.5"
implementation "org.koin:koin-android-scope:2.1.5"
implementation "org.koin:koin-android-viewmodel:2.1.5"
implementation "org.koin:koin-android-ext:2.1.5"
...
}
appModule.kt
module{ ... } 키워드를 이용해서 주입받고자 하는 객체의 집합을 모듈로 선언하고 변수에 저장한다.
single{ ... } 키워드를 사용해서 앱이 실행되는 동안 계속 유지되는 객체를 생성한다. repository, retrofit, db 등을 사용할 때 쓴다.
factory{ ... } 키워드를 사용해서 요청할 때마다 매번 새로운 객체를 생성한다. useCase, viewModel 등을 사용할 때 쓴다.
get() 호출 시 자동으로 알맞은 타입을 호출하여 컴포넌트 내에서 알맞은 의존성을 주입한다.
val appModule = module {
//Coroutine Dispatcher - Main, IO 를 싱클톤으로 만들어 준다
single { Dispatchers.Main }
single { Dispatchers.IO }
//Repository
single<TodoRepository> { DefaultRepository(get(), get(), get()) }
...
//UseCase
factory { GetToDoListUseCase(get()) }
...
//viewModel
factory { ListViewModel(get()) }
factory { MainViewModel() }
...
}
MyAppKotlinApplication.kt
Application 클래스를 상속받는 MyAppKotlinApplication 클래스를 생성한다.
onCreate() 에서 startKoin { ... } 을 호출하고 모듈을 넘겨준다.
class MyAppKotlinApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger(Level.ERROR)
androidContext(this@MyAppKotlinApplication)
modules(appModule)
}
}
}
AndroidManifest.xml
AndroidManifest.xml 에 Application 클래스를 등록해야 한다.
android:name=".MyAppKotlinApplication" 을 통해 만들어준 Application 클래스를 등록한다.
<application
android:name=".MyAppKotlinApplication"
...
</application>
MainActivity.kt
Activity 클래스에서 by inject() 를 사용하여 필요한 객체를 주입한다.
inject() 함수를 명시적으로 선언하여 주입을받아 사용한다.
internal class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by inject()
//private val viewModel by inject<MainViewModel>()
...
}
감사합니다 :)
'📱 안드로이드 Android ~ Kotlin' 카테고리의 다른 글
[Kotlin/코틀린] 1 - 변수/상수, 데이터타입, 함수, 컬렉션 타입 (0) | 2022.02.04 |
---|---|
[안드로이드/Android] Kotlin/Java 권한 요청 uses-permission (0) | 2021.12.30 |
[Android/Kotlin] FCM 푸시 Push 알림 구현하기 (2) | 2021.09.25 |
[Android/Kotlin] 구글 Google 계정 로그인 연동을 위한 firebase console 설정방법 및 소스코드 (0) | 2021.09.22 |
[Android/Kotlin] 코루틴 coroutine (0) | 2021.09.21 |