본문 바로가기

코틀린

의존성 주입(Dependency Injection) 코틀린

(안드로이드 공식문서를 참고하고 작성한 글입니다. 코틀린으로 설명되어 있어서 작성해봅니다.)
 
의존성 주입이란?
하나의 객체가 다른 객체에 의존성을 제공해주는 기술을 의미한다.
 
의존성 주입 장점
의존성 주입을 하면 아래와 같은 이점이 존재한다.
  • 코드 재활용
  • 리팩토링하기 편함
  • 테스팅 하기 편함

의존성 주입 방법

의존성 주입은 두가지가 존재한다.
  1. 생성자에 의한 의존성 주입
  2. 멤버변수에 의한 의존성 주입
 
하나씩 알아보기로 하고 일단 의존성 주입이 되지 않은 코드부터 한번 살펴보자.
공식문서를 읽다가 느낀건데 의존성 주입을 하지 않은 것은 객체간의 결합도가 높은 것과 같다.
일단 의존성 주입이 되지 않은 코드를 보고 불편한 점이 무엇인지 살펴보자.
의존성 주입이 되지 않은 코드
class Car {
    // 생성자에 의해 객체가 만들어진 시점부터 다른 구현체나 객체로 갈아끼울 수 없다. 자바로 치면 new 연산자를 한 시점부터 답이 없다는 말이다.
    private val engine = Engine()
    fun start() {
        engine.start()
    }
}

fun main(){
    val car : Car = Car()
    car.start()
}

class Engine {
    fun start(){
    }
}

 

위의 코드에서 문제점이 두가지 있다.
Car 클래스와 Engine 클래스의 결합도가 매우 높다는 점이다.
만약 Engine 말고도 SuperEngine, MotorEngine, UltraEngine 같은 새로운 객체들이 추가된다고 치면
Car 클래스는 이에 맞춰서 Engine을 갈아끼울 수 없다.
의역하면 다른 서브클래스나 구현체로 바꿔서 사용할 수 없다는 말이다.
Car 가 자신의 Engine 객체를 만들면, Car 객체 하나로 다른 타입의 Car 객체를 바꿔 넣을 수 없고 서로다른 두개의 Car 객체를 만들어야 한다.
위의 상황을 그림으로 표현하면 아래와 같다.
의존성 주입된 코드
1. 생성자에 의한 의존성 주입
class Car(private val engie : Engine) {
    fun start(){
        engie.start()
    }
}

fun main(){
    val engie = Engine()
    val car : Car = Car(engie)
    car.start()
}
 
이걸 의존성 주입이라고 한다. 의존성 주입이 되지 않았을때와는 다르게 생성자의 매개변수로 객체를 넣는다.
이렇게 하면 장점이 무엇이냐?
 
메인함수에서 Car 객체를 사용한다. Car 객체가 Engine 객체에 의존하기 때문에 앱은 Engine 객체를 만들고 이는 Car객체를 만들기 위해서 사용된다.
의존성 주입의 이점은 아래와 같다.
 
-Car 객체를 재사용할 수 있다. (다양한 Engine 구현체를 Car 객체에 담을 수 있다.)
-Car 객체를 테스트하기 쉬워진다. 다양한 구현체를 넣어서 코드가 동작하는지 확인할 수 있기 때문에 다양한 상황에서 메소드가 의도대로 동작하는지 파악하기 편하다.
멤버변수를 활용한 의존성 주입
class Car() {
    lateinit var engine: Engine
    fun start(){
        engine.start()
    }
}

fun main(){
    val car = Car()
    car.engine = Engine()
    car.start()
}
이렇게 의존성 주입하는 것을 dependency injection in hand 또는 manual dependency injection 이라고 부른다.
이런 것들을 수동으로 의존성을 주입했다고 표현한다.
(라이브러리를 사용하지 않고 의존성 주입을 하는케이스라고도 한다.)
수동으로 의존성 주입을 했을때 단점
1.규모가 큰 앱에서 각 layer 별로 의존관계를 설정하는 것은 많은 양의 boilerplate 코드를 필요로 한다.
앱이 커질수록 서로 다른 클래스의 의존관계를 설정해야 하는 일이 많아지는데 이것 자체가 귀찮고 이렇게 하면 boilerplate가 생긴다.
layer가 여러개인 상황의 프로젝트에서 최상단의 layer 객체를 사용하려면 아래쪽에 있는 layer 객체를 전부 다 만들어야 쓸 수 있다.
예를들면 view -> viewModel -> useCase -> reopository 순으로 데이터가 흐른다고 치면 repository 안에 있는 데이터 쓰려고 view 객체부터 하나씩 다 만들어야 한다는 소리다.
이러한 문제를 해결하기위한 방법이 두가지가 있다.
(의존성을 생산하고 제공하는 과정을 자동화하는 방법이다!)
 
  1. Reflection - 런타임때 dependencies 에 연결해 문제를 해결한다.
  2. 컴파일타이밍에 종속성(dependencies)을 연결하는 코드를 생성해서 static으로 문제를 해결하는 방법
 
Daggar 라이브러리를 사용하면 자바, 코틀린, 안드로이드 개발환경에서 의존성 주입을 편하게 할 수 있다.
 
 
의존성 주입의 대안책
의존성 주입 대책으로 사용 하는 것이 service locator다.
 
의존성 주입이 무엇인지에 까지 관해서만 공부를 했고 의존성 주입을 하기위한 자동화 방법이나 의존성 주입 라이브러리는 따로 정리하지 않았다.