[DI] 수동 종속항목 삽입 방법

2021. 11. 14. 17:34카테고리 없음

종속 항목이란?


클래스를 구성할 때 주로 다른 클래스의 오브젝트를 참조하게 됩니다. 예로 Car class가 Engine Class가 필요한 것처럼 하나의 클래스에서 필요한 다른 클래스를 종속 항목이라 합니다. (또는 의존성 주입이라고도 하지만 저는 종속 항목이란 말이 조금 더 와닿아서 해당 개념을 사용하는 걸 선호합니다.)

 

종속 항목 삽입이란?

말 드래도 종속 항목을 삽입하는 것을 의미합니다. 예로 특정 클래스가 필요한 객체를 얻는 방법 중 2가지를 알아보겠습니다.

 

1. 특정 클래스 내부에서 다른 클래스 객체를 생성하는 방법

 

2. 객체를 매개변수로 제공받는 법 

 

이 두가지 중 종속 항목 삽입은 2번에 해당합니다. 1번은 car 클래스 내부에 Engine class를 생성하는 것과 같은 예입니다. 물론 하나의 클래스가 다른 클래스를 필요로 한다는 조건은 충족하나, 이는 car와 Engine이 너무 밀첩하게 연관되어 있습니다. 때문에 휘발유를 사용하는 Engine과 전기를 사용하는 Engine 두가지라면 각각의 Car 클래스를 만들어야 합니다. 때문에 이는 종속 항목 "삽입"의 예보단 종속 항목 "의존성"에 더 가깝습니다. 아래 코드를 보며 더 이해해 보겠습니다.

class GasCar {

    private val engine = GasEngine()

    fun getEngineType():String{
        return engine.getType()
    }
}


fun main() {
    val car = GasCar()

    println(car.getEngineType())
}

1번처럼 GasCar class 내부에는 GasEngine class를 구현하고 있습니다. 그럼 ElectornicEngine을 사용하는 Car라면 별도의 ElectoricCar class를 만들고 내부의 Engine은 ElectonicEngine으로 변경해야 할 것입니다. 이처럼 동일한 Car인데 Engine이 변경되면 다른 Car class를 생성해야합니다. 

 

그럼 2번처럼 구현해보면 어떻게 될까요? 먼저 Engine을 공통적으로 사용하니 별도의 interface를 만들고 각각 GasEngine과 ElectonicEngine이 Engine을 상속받는 형태로 구현해보겠습니다. 

interface Engine {
    abstract fun getType(): String
}

 

class GasEngine : Engine {

    override fun getType(): String {
        return "Gas Engine"
    }
}
class ElectronicEngine : Engine {
    override fun getType(): String {
        return "Electronic Enigne"
    }
}

 

그리고 Car는 Engine을 내부적으로 선언하여 받는 것이 아닌, 매개변수로 Engine을 할당받는 형태로 만듭니다. 2번처럼요!!

class Car {
    laitinit var engine : Engine
    
    fun getEngineType():String{
        return engine.getType()
    }
}

fun main() {
    val gasEngine = GasEngine()
    val electronicEngine = ElectronicEngine()
    
    val car = Car()
    car.engine = gasEngine
    
    println(car.getEngineType())
}

1번과 다르게 2번의 구현 방법은 Engine을 Car class에서 선언하는 방식이 아닌 main()로부터 Engine()을 전달 받습니다.

따라서 Car 클래스를 Engine의 형태만 바꾸어 재사용할 수 있는 코드가 됩니다. 

 

그럼에도 불구하고 상용구 코드(보일러플레이트 코드)가 너무 많을 뿐더러 의존성이 여전히 강한 코드입니다. 2번처럼 구현을 하더라도 아래와 같은 1), 2)의 문제가 발생할 수 있습니다.

 

1) 만약 해당 Car를 main에서만 사용하는 것이 아닌 다양한 클래스에서 사용한다면 매번 Engine을 선언하고 Car객체를 선언해야 한다.

 

2) GasEngine이나 ElectronicEngine class 내부 구성이 바뀌면 해당 Engine을 선언한 모든 class가 변경이 필요하게 된다.

 

때문에 종속 항목들이 반복적으로 사용되는 일을 한 곳에서 빼다 쓰면 더 효율적으로 보일 것 같습니다.

즉 이처럼 종속 항목을 한 곳에서 가져다 쓰는 방법을 Container라고 하며 Locator design Pattern이라고도 합니다

object CarContainer {
    private val gasEngine = GasEngine()
    private val electronicEngine = ElectronicEngine()

    private val car = Car()

    fun getGasCar(): Car {
        car.engine = gasEngine
        return car
    }

    fun getElectronicCar(): Car {
        car.engine = electronicEngine
        return car
    }
}

fun main() {
    val car = CarContainer.getGasCar()
    
    println(car.getEngineType())
}

여기서 Container는 바로 CarContainer class입니다. CarContainer 클래스에서 engine과 car를 모두 갖고 있으며 필요에 따라 main()에게 전달을 해줍니다. 이렇게 종속 항목을 한 곳에 모아 제공하는 방식인 Locator pattern을 사용하면 Engine 내부의 변화가 발생하더라도 CarContainer 한 곳만 수정하게 되므로 2번의 구현보다 훨씬 더 의존성을 줄일 수 있습니다. 

 

종속 항목 삽입의 장점

1. 클래스 재사용 가능 및 종속 항목 분리

: 종속 항목들을 Container를 통해 쉽게 교체할 수 있습니다. 또한 종속 항목 생성 및 제어를 Container가 관리하기 때문에 해당 종속 항목을 사용하는 클래스에서는 종속항목을 제어할 필요가 사라집니다. 이로인해 코드 재사용이 개선될 수 있습니다.

 

2. 테스트 편의성

: 클래스는 종속 항목을 관리하지 않기에 클래스에 구현된 비지니스 로직을 테스트하기에 용이해 집니다. 

 

 

종속 항목 삽입의 단점

1. 코드 테스트가 쉽지 않습니다. Container는 전역적으로 동작하는 서비스이기 때문에 해당 container를 통해 종속 항목을 받는 클래스들이 모두 상호작용하기 때문입니다. 

 

2. Container를 전체 기간동안 관리하는 것이 아닌 특정 기간으로 범위를 잡는 경우 관리가 어렵습니다. 

 

 

지금까지 수동 종속 항목 삽입에 대해 공부했습니다. 수동 종속 항목 삽입을 공부하면서 Container를 작성해 의존성을 줄이는 방법도 좋지만 단점도 분명하다는 것을 깨닫게 되었습니다. 다음엔 Hilt를 사용하여 어떻게 주명 주기를 자동을 관리하고 수동 종속 항목 삽입보다 확장성이 좋은지를 알아보겠습니다~