본문 바로가기

안드로이드/DI

라이브러리 없이 DI 구현

 Locarm이라는 프로젝트를 진행하면서 DI 라이브러리 없이 DI를 구현해보는 연습을 하였다.

 

DI를 구현할 수 있는 방법은 AppContainer와 Factory 방식이 있다.

 

1. AppContainer

AppContainer 방식은 하나의 Container에서 객체를 관리하고, 이를 Application에 저장하여 사용하는 방식으로 다음과 같이 사용할 수 있다.

class AppContainer(context: Context) {

    val database by lazy {
        Room.databaseBuilder(context, DataBase::class.java, "db").build()
    }

    val repository by lazy {
        Repository(database.dao())
    }
}

 

class MyApplication: Application(){
    val container by lazy { AppContainer(this) }
    
}

val Context.appContainer: AppContainer
    get() = (applicationContext as MyApplication).container

 

class MainActivity: AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels {
        MainViewModelFactory(applicationContext.appContainer.locationRepository)
    }
}

 

이렇게 사용할 경우 나중에 전달 할 객체가 많아진다면 AppContainer의 크기가 매우 커질 것이고 관리가 어려워 질 것이다.

2. Factory Pattern

팩토리 패턴은 객체의 생성을 캡슐화하여 제공하는 디자인 패턴이다.

쉽게 말해 팩토리 클래스를 만들고 팩토리 클래스를 통해 객체를 생성 및 제공하는 방법으로 다음과 같이 사용할 수 있다.

 

1. DatabaseFactory 

object DatabaseFactory {

    fun createDatabase(): DataBase {
        return Room.databaseBuilder(
            MyApplication.instance,
            DataBase::class.java,
            "db"
        )
            .fallbackToDestructiveMigration(false)
            .build()
    }
}

DatabaseFactory를 생성하여 createDatabase()를 통해 DataBase 객체를 제공한다.

 

2. DaoFactory

object DaoFactory {
    private val database = DatabaseFactory.createDatabase()
    
    fun createFavoriteDao(): FavoritesDao = database.favoriteDao()
}

DaoFactory를 생성하여 Database 객체를 초기화하고, Database 객체를 통해 Dao를 제공한다.

이때, DaoFactory는 object로 선언되어 Database객체를 싱글톤으로 관리한다. (Database 객체는 Dao에서만 사용되기 때문)

 

3. DatasourceFactory

object DataSourceFactory {
    private val favoritesDao = DaoFactory.createFavoriteDao()
    private val favoritesDataSource: FavoritesDataSource = FavoritesDataSource(favoritesDao)

    fun createFavoritesDataSource(): FavoritesDataSource = favoritesDataSource
}

DatasourceFactory도 마찬가지로 Dao와 DataSource를 초기화 한 뒤, createFavoriteDateSource()를 통해 제공하여 싱글톤으로 관리한다.

 

Repository도 위와 마찬가지로 구현하면 되고, 이를 사용하는 ViewModel에서는 다음과 같이 쉽게 객체를 주입하여 사용할 수 있다.

class MainViewModel(
    private val locationRepository: LocationRepository,
) : ViewModel() {

    companion object {
        val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(
                modelClass: Class<T>,
                extras: CreationExtras
            ): T {
                return MainViewModel(
                    RepositoryFactory.createLocationRepository(), //Factory로 객체 주입
                ) as T
            }
        }
    }
}

 

팩토리 패턴의 예제에서의 문제는 다음과 같다.

  1. DB와 Dao를 object 전역 변수로 생성해놓고 싱글톤으로 제공하고 있는데, 이 경우 팩토리 패턴보단 오히려 AppContainer의 역할에 더 가깝다.
  2. 팩토리 패턴에 맞게 함수 호출 시 객체를 생성해서 전달하면 여러 객체가 필요 없는 Database와 Repository 등의 객체가 여러개 생성되면서 메모리 낭비가 될 수 있다.

AppContainer와 팩토리 패턴 혼합하여 사용

Database와 Dao, Repository와 같이 여러 객체가 생성될 필요가 없는 싱글톤 객체의 경우 AppContainer를 사용하여 관리하고, 싱글톤 객체가 아닌 경우 팩토리 패턴을 이용하여 의존성 주입하는 방법으로 구현하는 것도 좋은 방법인 것 같다.

제일 좋은 방법은 이러한 단점들을 보완하기 위해 등장한 DI 라이브러리를 사용하는 것이지 않을까...

'안드로이드 > DI' 카테고리의 다른 글

Hilt 동작 원리  (0) 2026.01.02
Service Locator  (0) 2025.03.06
[안드로이드] Hilt  (0) 2024.04.04
[안드로이드] Dependency Injection (DI)  (0) 2024.04.02