Beeeam

의존성과 의존성 주입(DI) + Hilt란? 본문

Android

의존성과 의존성 주입(DI) + Hilt란?

Beamjun 2023. 4. 7. 20:52

의존성이라는 용어를 많이 접했었다. 하지만 이게 정확하게 무엇인지 잘 모르고 있었다. 근데 이번에 Hilt 라이브러리를 공부하게 되면서 공부한 내용을 정리하려고 한다.

객체 지향 프로그래밍?

단어 그대로 객체 중심으로 프로그램을 설계하고, 개발하는 방식이다. 여기서 포인트는 객체가 중심이 되어서 “누가 어떤 일을 할 것인가?” 가 핵심이 된다.

객체를 중심으로 프로그램을 설계하기 때문에 객체 간의 협력은 필수적이다. 그래서 객체 간의 협력을 하게 되는데 이때 의존성이 생긴다.

의존성?

다른 것에 의지하여 생활하거나 존재하는 성질, 금단 증상 때문에 계속하여 약물을 섭취하지 않으면 안 되는 상태.

객체 지향에서 의존성

한 객체나 클래스가 파라미터나 리턴 값 또는 지역 변수 등으로 다른 객체를 참조하는 것

class A {
    val b = B()
    var n = b.getNum(1, 2)
}

class B {
    fun getSum(n1 : Int, n2 : Int): Int {
        return n1 + n2
    }
}

위의 코드에서 A 클래스는 B 클래스의 메서드를 호출한다. 이 처럼 한 클래스가 수행되기 위해서 다른 클래스가 필요한 경우에 두 클래스가 의존 관계가 있다고 한다. (위와 같은 경우에는 A 클래스가 B에 의존한다고 표현)

 

의존성이 위험한 이유

말 그대로 의존을 하고 있기 때문에 위험하다.

예를 들어 A 클래스가 B 클래스에 의존하고, B 클래스는 C 클래스에 의존한다고 하자. 근데 C 클래스에 변경이 생겼다. 그러면 B 클래스와 A 클래스 둘 다 변경을 해야 하는 경우가 생길 수 있다.

즉, 한 클래스의 변경 사항이 의존 관계를 맺고 있는 다른 클래스들에 연쇄적인 변경을 요구할 수 있다. 이를 의존성 전이라고 한다.

class A {
    val n = 100

    fun getTwice() {
        var num = B().two * n
    }
}

class B {
    val two = 2
}
class A {
    val n = 100

    fun getTwice() {
        var num = B().three * n
    }
}

class B {
    val three = 3
}

위의 두 코드를 비교해보자. 첫 번째 코드의 class B의 변수명이 바뀌자 class A에서도 변경이 일어났다. 이렇게 의존 관계

 

유닛 테스트는 특정 모듈이 개발자의 의도대로 잘 동작하는지 확인 하는 작업이다. 이러한 테스트의 목적은 프로그램의 각 부분이 정확하게 동작하는지 확인하는 것인데 각 모듈들이 의존 관계가 있으면 해당 모듈에 대한 독립적인 테스트를 진행하기 힘들다.

힘든 것이지 불가능 한 것은 아니다. 의존하고 있는 객체, 모듈을 임시 객체로 사용하면 유닛 테스트를 할 수 있다. 아니면 바로 밑에서 언급할 의존성 주입을 사용하면 된다.

그리고 코드의 재사용도 어렵게 만든다.

이러한 이유들 때문에 의존성 주입을 사용한다.

 

의존성 주입?

의존성 주입은 의존성을 외부에서 주입하는 것이다. 더 자세히 말하면 필요한 객체를 직접 생성하거나 찾지 않고, 외부에서 필요한 객체를 주입하는 것이다.

의존성을 주입하는 방법은 여러 가지 있다.

  1. 생성자 주입
  2. 필드 주입
  3. 수정자 주입(Setter 주입)
  4. 일반 메서드 주입

안드로이드는 생성자 주입필드 주입을 사용할 수 있다.

기존 코드

class aClass{
    fun getA(){   }
}
class bClass{
    private val a: aClass = aClass()

    fun getB() {
        a.getA()
    }
}
class cClass{
    val b: bClass = bClass()
    b.getB()
}

생성자 주입

class aClass(){
    fun getA(){   }
}
class bClass(private val a: aClass){
    fun getB() {
        a.getA()
    }
}
class cClass {
    val aClas = aClass()
    val b: bClass = bClass(aClas)
    b.getB()
}

클래스의 생성자를 통해서 필요한 객체를 주입

클래스가 생성될 때 의존성이 주입 된다.

 

필드 삽입

class aClass{
    fun getA(){   }
}
class bClass{
    lateinit var a : aClass

    fun getB() {
        a.getA()
    }
}
class cClass {
    val b: bClass = bClass()
    b.a = aClass()
    b.getB()
}

필드 안에서 필요한 객체가 주입

클래스가 생성된 후 의존성이 주입 된다.

 

의존성 주입을 하면 어떤 점이 좋을까?

  • 코드 재사용 가능
  • 리팩터링 편의성
  • 테스트 편의성
  • 모듈 간의 의존성 하락

의존성 주입을 사용하면 위에서 의존성이 위험하다고 했던 이유들을 모두 해결할 수 있다.

 

Hilt

Hilt는 안드로이드에서 의존성 주입을 도와주는 라이브러리이다. 그리고 자동으로 수명주기도 관리를 해준다.

Hilt는 기존에 사용되던 Dagger 라이브러리를 기반으로 만들어졌다. Dagger보다 쉬워서 러닝 커브가 낮고, 초기 DI 환경 구축 비용을 절감할 수 있는 장점이 있다. 그리고 구글에서 지원하고 있는 jetpackAAC 라이브러리를 Hilt를 사용하여 의존성 주입을 쉽게 할 수 있도록 지원하고 있기 때문에 안드로이드에 최적화된 라이브러리이다. 

 

Hilt vs Dagger vs Koin

안드로이드에는 DI(의존성 주입)을 도와주는 Koin과 Dagger 라이브러리가 있다. 그럼 본인은 왜 Hilt를 사용했을까?

위의 자료는 Hilt, Dagger, Koin을 비교한 것이다. 먼저 셋 다 Kotlin에서 사용할 수 있다. 하지만 Koin은 자바에서 사용할 수 없다. 이는 Kotlin DSL이기 때문이다.

러닝 커브 면에서는 Dagger가 높고 그에 비해 Koin과 Hilt는 낮다. 그리고 결정적으로 Hilt, Dagger는 에러 검출 시점이 컴파일 시점이다.

그래서 모든 언어에서 사용 가능하고, 러닝 커브도 낮고, 에러 검출 시점이 컴파일 시점인 Hilt가 가장 장점이 많다고 판단하여 Hilt를 사용하였다.

 

참조

https://hudi.blog/dependency-injection/

 

 

Hilt에 대한 자세한 내용은 다음에