일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 옵저버 패턴
- 빌터패턴
- android designsystem
- material3
- 디자인패턴
- designPattern
- builderPattern
- factory method
- Abstract Factory
- 추상팩토리패턴
- 추상 팩토리
- 프로토타입 패턴
- Design Pattern
- Kotlin
- 싱글톤
- ㅓ
- 안드로이드 디자인시스템
- Functional Programming
- Singleton
- 코틀린
- 팩토리 메소드
- 디자인패턴 #
- El
- Observer Pattern
- compose
- F
- 함수형프로그래밍
- r
- PrototypePattern
- ㅋㅁ
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
수동 종속성 삽입 본문
클래스 간 종속성은 그래프로 표시할 수 있고 그래프에서 각 클래스는 종속된 클래스에 연결됩니다. 모든 클래스와 서로의 종속성을 표시하면 애플리케이션 그래프가 구성됩니다. 위 그림은 애플리케이션 그래프의 추상적 개념을 보여줍니다. 클래스 A(ViewModel)가 클래스 B(Repository)에 종속하면 A에서 B까지 가리키는 선이 종속성을 나타냅니다.
종속성 삽입을 사용하면 클래스를 쉽게 연결할 수 있고 테스트를 위해 구현을 교체할 수 있습니다. 예를 들어 저장소에 종속된 ViewModel을 테스트할 때 fake나 mock과 함께 Repository의 다른 구현을 전달하여 다른 사례를 테스트할 수 있습니다.
수동 종속성 삽입의 기본사항
흐름은 앱에서 기능에 상응하는 화면 그룹이라고 간주합니다. 로그인, 등록, 결제가 모두 흐름의 예입니다.
일반적인 Android 앱의 로그인 흐름을 다룰 때
LoginActivity는 LoginViewModel에 종속하고
LoginViewModel은 UserRepository에 종속하고
UserRepository는 UserLocalDataSource와 UserRemoteDataSource에 종속하고
UserLocalDataSource와 UserRemoteDataSource는 Retrofit 서비스에 종속합니다.
class UserRepository(
private val localDataSource: UserLocalDataSource,
private val remoteDataSource: UserRemoteDataSource
) { ... }
class UserLocalDataSource { ... }
class UserRemoteDataSource(
private val loginService: LoginRetrofitService
) { ... }
class LoginActivity: Activity() {
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// In order to satisfy the dependencies of LoginViewModel, you have to also
// satisfy the dependencies of all of its dependencies recursively.
// First, create retrofit which is the dependency of UserRemoteDataSource
val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
// Then, satisfy the dependencies of UserRepository
val remoteDataSource = UserRemoteDataSource(retrofit)
val localDataSource = UserLocalDataSource()
// Now you can create an instance of UserRepository that LoginViewModel needs
val userRepository = UserRepository(localDataSource, remoteDataSource)
// Lastly, create an instance of LoginViewModel with userRepository
loginViewModel = LoginViewModel(userRepository)
}
}
위과 같은 방법은 다음과 같은 문제가 있습니다.
- 상용구 코드가 많습니다. 코드의 다른 부분에서 LoginViewModel의 다른 인스턴스를 만들려면 코드가 중복될 수 있습니다.
- 종속성은 순서대로 선언해야 합니다. UserRepository을 만들려면 LoginViewModel 전에 인스턴스화해야 합니다.
- 객체를 재사용하기가 어렵습니다. 여러 기능에 걸쳐 UserRepository를 재사용하려면 싱글톤 패턴으로 만들어된다. 그렇게 되면, 모든 테스트가 동일한 싱글톤 인스턴스를 공유하므로 싱글톤 패턴을 사용하면 테스트가 더 어려워집니다.
컨테이너로 종속성 관리
객체 재사용 문제를 해결하려면 종속 항목을 가져오는 데 사용하는 자체적으로 종속 항목 컨테이너 클래스를 만들어보자.
이 컨테이너에서 제공하는 모든 인스턴스는 공개될 수 있습니다.
다음 예에서는 UserRepository의 인스턴스만 필요하므로 나중에 제공해야 하는 경우 공개하는 옵션으로 종속 항목을 비공개할 수 있습니다.
// 앱 전반에 걸쳐 공유 객체의 컨테이너 클래스.
class AppContainer {
//userRepository 외부 노출전 사전에 필요한 종속성을 먼저 삽입한다.
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
// userRepository 는 외부에 노출시켜야되기 때문에 공개한다.
val userRepository = UserRepository(localDataSource, remoteDataSource)
}
이러한 종속 항목(UserRepository)은 전체 애플리케이션에 걸쳐 사용되므로 모든 Activity에서 사용할 수 있는 일반적인 위치,
즉 애플리케이션 클래스에 배치해야 합니다. AppContainer 인스턴스가 포함된 맞춤 애플리케이션 클래스를 만듭니다.
// 커스텀 application 객체를 커스텀 합니다.
class MyApplication : Application() {
// 앱의 모든 activity/fragment에 사용될 컨테이너 클래스 생성
val appContainer = AppContainer()
}
참고: AppContainer는 Application 클래스에 배치된 앱 전체에서 공유하는 고유한 인스턴스가 있는 일반적인 클래스일 뿐이고, AppContainer는 싱글톤 패턴으로 만들어져 있지 않다.
(Kotlin에서는 object가 아니고 자바에서는 일반적인 Singleton.getInstance() 메서드로 액세스되지 않습니다)
class LoginActivity: Activity() {
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Gets userRepository from the instance of AppContainer in Application
val appContainer = (application as MyApplication).appContainer
loginViewModel = LoginViewModel(appContainer.userRepository)
}
}
싱글톤 UserRepository를 얻을수 없습니다. (AppContainer는 싱글톤 패턴을 따르지 않기 때문에)
대신에, AppContainer는 Activity(그래프의 객체를 포함하고 다른 클래스가 사용할 수있는 객체의 인스턴스를 만드는)에 공유된다.
LoginViewModel이 애플리케이션의 더 많은 위치에 필요하면, 한 곳에서 LoginViewModel의 인스턴스를 생성하는 것이 좋다.
LoginViewModel의 생성을 컨테이너로 이동시키고 팩토리(ViewModelFactory)를 제공할 수 있습니다.
// 팩토리 인터페이스를 정의
interface Factory {
fun create(): T
}
// 뷰모델 팩토리
// 뷰모델은 레포지터리에 의존성을 가지고 있기 때문에, 뷰모델을 생성하기 위해서 팩토리의 생성사 파라미터로
// 레포지터리 객체를 받아야 한다.
class LoginViewModelFactory(private val userRepository: UserRepository) : Factory {
override fun create(): LoginViewModel {
return LoginViewModel(userRepository)
}
}
// AppContainer는 팩토리를 이용해 뷰모델을 제공한다.
class AppContainer {
...
val userRepository = UserRepository(localDataSource, remoteDataSource)
val loginViewModelFactory = LoginViewModelFactory(userRepository)
}
class LoginActivity: Activity() {
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// applicatin 객체의 appcontainer로 부터 팩토리 얻어서 로그인 뷰모델 객체생성.
val appContainer = (application as MyApplication).appContainer
loginViewModel = appContainer.loginViewModelFactory.create()
}
}
이전 방식보다 좋지만 여전히 다음과 같은 문제를 고려해야 합니다
- AppContainer를 직접 관리하여 모든 종속 항목의 인스턴스를 수동으로 생성
- 객체를 재사용할지에 따라 수동으로 팩토리나 매개변수를 생성
애플리케이션 흐름에서 종속성 관리
프로젝트에 기능을 더 많이 포함하려 할 때 AppContainer는 복잡해집니다.
앱이 커지고 다양한 기능 흐름을 도입하기 시작하면 더 많은 문제가 발생합니다.
흐름이 다양하면 객체가 흐름의 범위에만 있기를 원할 수 있습니다. 예를 들어 로그인 흐름에서만 사용되는 사용자 이름과 비밀번호로 구성될 수 있는 LoginUserData를 만들 때 개발자는 다른 사용자의 이전 로그인 흐름의 데이터를 유지하지 않으려고 합니다.
개발자는 모든 새 흐름에 새 인스턴스를 원합니다. 다음 코드 예와 같이 AppContainer 내에 FlowContainer 객체를 만들면 가능합니다.
애플리케이션 그래프와 FlowContainer 를 최적화하는 것도 어려울 수 있습니다. 흐름에 따라 필요하지 않은 인스턴스를 삭제해야 합니다.
LoginActivity와 여러 프래그먼트(LoginUsernameFragment, LoginPasswordFragment)로 구성된 로그인 흐름을 가정해보겠습니다. 이러한 뷰는 다음과 같이 하려고 합니다.
- 로그인 흐름이 완료될 때까지 공유해야 하는 동일한 LoginUserData 인스턴스에 액세스합니다.
- 흐름이 다시 시작되면 LoginUserData의 새 인스턴스를 만듭니다.
로그인 흐름 컨테이너로 이 작업을 완수할 수 있습니다. 이 컨테이너는 로그인 흐름이 시작될 때 만들어지고 흐름이 끝날 때 메모리에서 삭제되어야 합니다.
앱에서 LoginContainer 인스턴스를 여러 개 만들 수 있으려면 싱글톤으로 만들지 말고 로그인 흐름에 필요한 AppContainer의 종속 항목이 있는 클래스로 만듭니다.
class LoginContainer(val userRepository: UserRepository) {
val loginData = LoginUserData()
val loginViewModelFactory = LoginViewModelFactory(userRepository)
}
// AppContainer contains LoginContainer now
class AppContainer {
...
val userRepository = UserRepository(localDataSource, remoteDataSource)
// LoginContainer will be null when the user is NOT in the login flow
var loginContainer: LoginContainer? = null
}
흐름 관련 컨테이너가 있으면 컨테이너 인스턴스를 언제 만들고 삭제할지 판단해야 합니다.
로그인 흐름이 LoginActivity에 포함되어야 해서, 로그인 컨테이너의 수명 주기를 관리하는 것은 Activity입니다.
LoginActivity는 onCreate()에서 인스턴스를 만들고 onDestroy()에서 삭제할 수 있습니다.
class LoginActivity: Activity() {
private lateinit var loginViewModel: LoginViewModel
private lateinit var loginData: LoginUserData
private lateinit var appContainer: AppContainer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appContainer = (application as MyApplication).appContainer
// Login flow has started. Populate loginContainer in AppContainer
appContainer.loginContainer = LoginContainer(appContainer.userRepository)
loginViewModel = appContainer.loginContainer.loginViewModelFactory.create()
loginData = appContainer.loginContainer.loginData
}
override fun onDestroy() {
// Login flow is finishing
// Removing the instance of loginContainer in the AppContainer
appContainer.loginContainer = null
super.onDestroy()
}
}
결론
종속성 삽입은 확장 및 테스트 가능한 Android 앱을 만드는 데 유용한 기법입니다.
컨테이너를 사용하여 앱의 다양한 부분에서 클래스 인스턴스를 공유하고 한 곳에서 팩토리를 사용하는 클래스 인스턴스를 만드세요.
애플리케이션이 커지면 상용구 코드(예: 팩토리)를 많이 작성하게 되고 상용구 코드는 오류가 발생하기 쉽습니다.
컨테이너의 범위와 수명 주기를 직접 관리하여 메모리를 확보하기 위해 더 이상 필요하지 않은 컨테이너를 최적화 및 삭제해야 합니다. 이 작업을 잘못하면 앱에서 미묘한 버그와 메모리 누수가 발생할 수 있습니다.
다음 페이지에서는 Dagger를 사용하여 이 프로세스를 자동화하고, 자동화하지 않고 수동으로 작성했을 때와 동일한 코드를 생성할 수 있는 방법을 알아봅니다.
'DI > Dagger2' 카테고리의 다른 글
Android app에서 Dagger 사용 (0) | 2021.05.03 |
---|---|
Dagger Basic (0) | 2020.11.30 |
Android의 종속 항목 삽입 (0) | 2020.11.24 |
Dagger2 (안드로이드 의존성 주입 프레임워크)란? (0) | 2020.08.04 |
의존성 주입이란? (0) | 2020.08.04 |