모바일/Android

Android에서의 의존성 주입(DI)이란?

Patti Smith 2024. 8. 6.

Overview

객체지향 프로그래밍의 설계 패턴 중 하나로 한 클래스가 다른 클래스를 참조할 때 참조된(필요한) 클래스를 종속 항목이라고 하며, 이때 내부에서 직접 생성하는 것이 아니라 외부에서 주입을 함으로써 의존성을 제공하는 기술이다. 여기서 필요한 클래스를 매개변수로 제공받으며 클래스 인스턴스가 자체적으로 종속 항목을 얻는 대신 매개변수로 종속 항목을 받는다. 

 

다음은 클래스 내부에서 자체적으로 종속 항목을 받는 코드 예시다.

class Car {

    private val engine = Engine()

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

fun main(args: Array

 

이 코드는 두 가지 문제가 있을 수 있다.

 

  1. Car와 Engine은 밀접하게 연결이 되어 있어 다른 클래스로 대체하기가 어렵다. 만약 Gas나 Electric 유형의 엔진을 사용하게 된다면 Car를 재사용할 수 없고 비슷한 클래스인 Car를 두 개 생성해야 한다.
  2. Engine 클래스의 종속성이 높아 테스트를 하기 어렵다. 

 

이제 의존성 주입을 통해 Car 클래스를 설계해보자.

 

1) 생성자 주입

class Car(private val engine: Engine) {
	fun start() {
    	engine.start()
    }
}

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

 

Car는 Engine에 종속되어 Engine 인스턴스를 생성한 뒤 Car 인스턴스를 생성한다. 이 방식을 통해 Engine의 다양한 구현을 Car에 전달할 수 있다. 예컨대 ElectricEngine이라는 새로운 Engine의 서브 클래스를 정의할 수 있다. 

 

2) 필드 삽입

특정 Android 프레임워크 클래스는 시스템에서 인스턴스화되어 생성자 삽입을 하기 어렵다. 따라서 필드 삽입을 통해 클래스가 생성한 뒤 종속 항목을 주입할 수 있다.

 

class Car{
	lateinit var engine: Engine
    	
    fun start(){
    	engine.start()
    }
}

 

위 두 예시는 라이브러리를 사용하지 않고 종속항목을 직접 생성하고 제공 및 관리했다. 이를 종속 항목 수동 삽입이라고 한다. 이 경우 종속 항목이 적기 때문에 간으했지만 만약 대규모 클래스의 경우 모든 종속 항목을 가져와 연결하려면 많은 양의 코드는 물론, 다중 레이어의 경우 최상위 객체를 생성할 경우 하위 레이어의 모든 항목을 가져와야 한다. 더군다다 종속 항목을 전달하기 전까지 종속항목을 구성할 수 없을 때 역시 걸림돌이 될 수 있다. 이 두 가지 문제를 해결하려면 Dagger 혹은 Guice 같은 라이브러리 사용이 필요하다.

 

의존성 주입의 대안

의존성 주입의 대안으로 Android에서는 service Locater을 제공한다. Service Locater은 구체적인 종속 항목으로부터 클래스가 잘 분리될 수 있도록 도와준다.

object ServiceLocator{
	fun getEngine(): Engine = Engine()
}

class Car {
	private val engine = ServiceLocator.getEngine()
    
    fun start() {
    	engine.start()
    }
}

 

서비스 로케이터를 사용하면 이전 방법들 처럼 외부에서 종속 항목을 주입시킬 수 있다는 점, 서브 클래스도 쉽게 대체 가능하다는 점이 있지만, 다음 같은 문제점이 있다.

 

  1. 매개변수로 전달되지 않으므로 클래스 외부에서 어떤 게 필요한지 알 수 없다.
  2. ServiceLocator는 object 키워드를 사용한 싱글톤으로 구현되기에 테스트 코드를 넣기가 상당히 곤란해진다.
  3. 2와 같은 이유로 ServiceLocator는 앱이 시작하고 초기화될 떄 그리고 종료될 때까지 유지된다. 즉 앱 전체 수명주기 외 범위다. 특정 범위에서 의존성 주입을 진행하기 어려워진다.

결론

의존성 주입에 대해 알아보았다. 의존성 주입은 클래스의 재사용을 통해 비효율을 막는다. 다른 클래스와의 직접적인 연결을 막아 Dependency Inversion Principle(DIP)을 만족시키며, 의존성 생성과 관리의 책임을 별도의 객체(DI container)에 위임하여 자신의 기능에만 집중할 수 있게 한다. 즉, Single Responsibility Principle(SRP)원칙을 만족시킨다. 

 

 

 

댓글