안드로이드 의존성 주입(DI)란 무엇이고 왜 쓰는가

SSY·2022년 12월 14일
1

DI

목록 보기
1/1
post-thumbnail

취업을 준비하거나 이직을 준비할 때 필요한 공통적인 기술 스택들이 있다.

  • 클린아키텍쳐 : MVVM아키텍쳐 패턴?
  • 비동기 프레임워크 : RX Kotiln, Coroutine등...?
  • 의존성 주입 프레임 워크 : Koin이나 Dagger2 또는 DaggerHilt등...?

우리는 위 안드로이드 기술들에 대해 '왜 쓰는가를 설명해보세요'라는 말을 들었을 때 설명하지 못하는 경우가 많다. 하지만 이러한 원리구조를 정확히 알고 써야 소프트웨어를 목적과 비즈니스에 맞게 설계할 수 있는거라 생각한다. 그래서 정리해볼까 한다.

이 포스팅은 개념적인 부분만 설명한다. 따라서 면접같은 곳에 도움이 꽤 될거라 생각합니다~!

의존성 주입(DI)란 무엇이고 왜 사용하는가

안드로이드 개발자로써...

의존성 주입이란?
클래스와 클래스간에 관계를 맺을 때, 내부에서 직접 생성하는 것이 아닌, 외부에서 주입을 함으로써 관계를 맺게 만드는 것을 의미한다.

조금 더 자세하게 들어가자면, 인터페이스화를 통하여 객체 변경에 대한 유연성을 증대시키며, 객체를 내부에서 생성하는 것이 아닌, Container에서 생성하여(=제어의 역전)주입하는 것을 의미한다.

정리
1. 인터페이스화를 통한 객체 참조 변경의 유연성 증대
2. 객체 내부에서 생성하는게 아닌, DI Container에서 생성하여 주입

그리고 이들은 크게 두 가지 관점으로 설명할 수 있다.

  1. 1개의 클래스 관점
  2. 다중 클래스 관점

코드로 설명을 해볼까 한다.

1. 1개의 클래스 관점

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

class Car {
    val engine = OilEngine()
    
    fun start() {
        engine.startOilEngine()
    }
}


class OilEngine {
    
    fun startOilEngine() {
        print("startOilEngine!!")
    }
} 

위와 같은 코드가 있다고 가정해보자. 자동차(=Car) 내부에는 엔진(=OilEngine)이 포함된다. 그래서 자동차 클래스 내부에 엔진 클래스를 포함시켰다. 그리고 자동차를 ON시키려 한다. 그러면 당연히 엔진도 시동이 걸릴 것이다. 그래서 start메소드를 호출했다. 그리고 저 프로그램은 현재로써는 괜찮게 돌아간다.

하지만, 요구사항의 변경이 발생한다. OilEngine을 ElectronicEngine으로 바꾸어 달라는 요구사항의 변경이다. 그래서 이를 다음과 같이 변경하려 했다.

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

class Car {
    val engine = ElectronicEngine() // OilEngine -> ElectronicEngine으로 변경함
    
    fun start() {
        engine.startOilEngine() // 이부분 컴파일 에러!
    }
}


class ElectronicEngine {
    
    fun startElectronicEngine() {
        print("startElectronicEngine!!")
    }
}

근데 자세히 보면 ElectronicEngine클래스의 메소드 명이 바뀌었다.(startOtilEngine -> startElectronicEngine)그래서 메소드 이름을 변경하지 않고 이대로 그냥 사용한다면, 컴파일 오류가 난다. 그럼 어떻게 해야할까? ElectronicEngine에 의존하고 있는 Car클래스 내부도 그에 맞게 변경해줘야 하는 것이다.

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

class Car {
    val engine = ElectronicEngine()
    
    fun start() {
        engine.startElectronicEngine() // 메소드명 변경 및 정상 동작!
    }
}


class ElectronicEngine {
    
    fun startElectronicEngine() {
        print("startElectronicEngine!!")
    }
}

물론, 위의 경우는 메소드 한줄만 바꾸어주면 끝이다. 하지만 만약에, 기존 OilEngine에 의존하고 있었던 부분들이 100군데였다면? 100군데를 모두 수정해야만 했을 것이다. 이러한 불편함은 어떻게 해결해야만 하는걸까?

인터페이스를 사용하여 객체 변경에 대한 유연성을 증대시키기

아래에는 Engine인터페이스를 정의했다. 그리고 자식(=ElectronicEngine)이 상속받게 함으로써 Engine종류 객체가 변경에 유연하게 할 수 있도록 했다. 즉, 엔진 객체가 OilEngine객체이든, ElectronicEngine객체이든 상관이 없다는 뜻이다.

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

class Car {
    val engine: Engine = ElectronicEngine()
                   OR
    val engine: Engine = OilEngine()
                   OR
    val engine: Engine = GasolineEngine()
    
    fun start() {
        engine.startEngine()
    }
}

interface Engine {
    fun startEngine()
}

class ElectronicEngine: Engine {
    
    override fun startEngine() {
        print("startElectronicEngine!!")
    }
}

class OilEngine: Engine {
    
    override fun startEngine() {
        print("startOilEngine!!")
    }
}

class GasolineEngine: Engine {
    
    override fun startEngine() {
        print("startGasolineEngine!!")
    }
}

정리
1. Engine인터페이스 설계
2. 하위에 자식 클래스 설계
3. main에서 부모 타입의 변수로 자식 클래스 참조

위와 같이 interface를 통하여 객체 변경에 대한 유연성을 증대시킬 수 있었다. Engine객체가 OilEngine이든, ElectronicEngine객체이든, 앞으로 새로 나올 GasolineEngine객체이든 말이다.

2. 다중 클래스 관점

하지만 그럼에도 불구하고 문제가 있다. 그건 바로, Car클래스 내부에는 Engine객체를 직접 포함하고 있다는 것이다. 다음과 같이 Car클래스 4개가 있다고 가정해보자. 그리고 각 Car클래스들은 OilEngine을 공통으로 사용하고 있다.

class SonataCar {
    val engine: Engine = OilEngine() // 이넘을 ElectronicEngine()으로 바꿔달라는 요구사항!
    
    fun start() {
        engine.startEngine()
    }
}

class AvanteCar {
    val engine: Engine = OilEngine() // 이넘을 ElectronicEngine()으로 바꿔달라는 요구사항!
    
    fun start() {
        engine.startEngine()
    }
}

class JenesissCar {
    val engine: Engine = OilEngine() // 이넘을 ElectronicEngine()으로 바꿔달라는 요구사항!
    
    fun start() {
        engine.startEngine()
    }
}

class PalisadeCar {
    val engine: Engine = OilEngine() // 이넘을 ElectronicEngine()으로 바꿔달라는 요구사항!
    
    fun start() {
        engine.startEngine()
    }
}

하지만 요구사항이 변경되었다. 전기차세상이 되면서 OilEngine -> ElectronicEngine으로 변경해달라는 요구사항이 들어온 것이다. 그러면 어떻게 해야할까?

각 Car클래스 내부에 정의되어 있는 OilEngine클래스를 모두 변경해야 하는 것이다. 만약 Car클래스가 100개였다면? 100개를 모두 일일이 변경하는 수밖에 없다. 그럼 이는 어떻게 해야할까?

여기서 바로 DI의 사용!
외부 Container에서 객체를 생성하게 함으로써(제어의 역전) 각 Car클래스 내부로 주입하기!

즉, Engine클래스 생성에 대한 권한은 Car클래스가 아니라, Conainer클래스가 담당하는 것이다. 중요해서 한번 더 강조하고 싶다

중요!
Engine클래스 생성에 대한 권한은 Car클래스가 아니라, Cotainer클래스가 담당

다음과 같이 말이다.

class SonataCar {

    @Inject
    lateinit var engine: Engine
    
    fun start() {
        engine.startEngine()
    }
}

class AvanteCar {

    @Inject
    lateinit var engine: Engine
    
    fun start() {
        engine.startEngine()
    }
}

class JenesissCar {

    @Inject
    lateinit var engine: Engine
    
    fun start() {
        engine.startEngine()
    }
}

class PalisadeCar {

    @Inject
    lateinit var engine: Engine
    
    fun start() {
        engine.startEngine()
    }
}

@Module
@InstallIn(ApplicationComponent::class)
object EngineModule {

  @Provides
  fun provideElectronicEngine(): Engine {
      return ElctronicEngine()
  }
}

업로드중..

위는 새로운 컨테이너를 정의해 주었다.(=EngineModule) 그럼으로써 우리는 이제 Engine객체들을 OOOCar클래스 내부에서 관리하지 않아도 된다. 오로지, 새로 정의한 컨테이너(EngineModule)에서 이를 관리해주면 되는 것이다. (이로써 의존성 주입(DI)이 되었다)

즉, 기존에는 Engine객체를 관리하는 포인트가 4곳(각각의 Car클래스)이었다면, 이젠 1곳(=EngineModule)에서만 하면 된 것이다.

만약 우리가 OilEngine객체를 다시 주입시키고 싶다면? EngineModule만 수정하면 된다!

정리

  • 1개의 클래스 관점 : 인터페이스의 다형성을 활용하면 객체를 변경해야할 때, 수정을 최소화할 수 있다.
  • 다중 클래스 관점 : 제어의 역전을 활용하고 이를 주임함으로써 여러 클래스들의 변경사항을 최소화할 수 있다.

마치며
안드로이드 의존성 주입(DI)개념에 대해 알아보았다. 앞으로 이 글이 여기서 끝나는게 아닌, 비슷한 개념이 있으면 여기에 추가 보충해야겠다.

profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.

2개의 댓글

comment-user-thumbnail
2024년 5월 15일

쉬운 예시 감사합니다~^^

답글 달기
comment-user-thumbnail
2024년 10월 8일

1번의 내용은 추상화와 관련이 있어보이네요. 의존성 주입 과 추상화는 나누어서 보아야 한다고 보는데 어떻게 생각하시는지 궁금하네요. 그리구 2번 내용은 의존성 주입을 한다고 했을 때 결국 각 Car 클래스의 인스턴스를 생성자 코드에서 OilEngine() -> ElectronicEngine() 로 바꿔서 주입해주어야 하니 같은 결과가 아닐까요?

답글 달기