블로그명..?

[DI, 의존성 주입] 기본 개념 본문

Android/DI

[DI, 의존성 주입] 기본 개념

bs_choi 2021. 7. 21. 18:58

DI 란?

Dependency Injection의 약자. 의존성 주입을 뜻함.

 

특정 객체의 인스턴스를 외부에서 생성하여 전달하는 기법.

 

DI의 장점

  1. 재사용성을 높여준다.
  2. 테스트에 용이함
  3. 코드 가독성 증대
  4. 코드 단순화
  5. 결합도는 줄이고, 확장성과 유연성을 확보 가능

 

의존성이란?

class AClass {
    private val b = BClass()

    fun test(){
        b.action()
    }

}

class BClass {
    fun action(){
        print("Action B")
    }
}

위 처럼 A 클래스에서 내부에 B클래스의 변수를 사용하게 됨으로써,

 

A클래스는 B클래스에 대해 의존성을 가지게 된다.

-> B클래스가 변경되는 경우에 A클래스가 영향을 받게 된다.

 

주입이란?

클래스 내부가 아닌 외부에서 객체를 생성해서 넣어준다고 생각하면 된다.

 

주입은 2가지 타입을 생각해 볼 수 있다.

 

생성자 주입

class AClass(b : BClass){
    private val bClass = b

    fun test(){
        bClass.action()
    }

}

A클래스 생성단계에서 B클래스를 주입받는다.

 

필드 주입(Setter 주입)

class AClass(){
    lateinit var bClass : BClass

    fun test(){
        bClass.action()
    }

}

class main(){
    fun start(){
        val a  = AClass()
        a.bClass = BClass()
        a.test()
    }
}

A클래스 생성단계에서 B클래스를 주입받지 않고,

 

외부에서 직접 B클래스를 생성하여 A클래스에 Setter 또는 멤버변수에 넣어준다.

이를 필드 주입이라고 한다.

 

의존성 주입과 의존성 역전

위에서처럼 객체를 외부에서 생성하여 넘겨준다 한다고 해도, 진정한 의미의 DI가 성립하지는 않는다.

주입받은 B클래스가 수정이 되면 AClass도 수정이 필요할 수 있다는 사실은 동일하기 때문이다.

(지금은 BClass를 주입받고 있다.

하지만 BClass와 비슷하지만 다른 행동을 하는 CClass의 행동으로 수정하고 싶다면???

-> AClass의 생성자에 BClass 객체를 CClass로 수정하거나 BClass를 주입받는 메소드를 추가해야 할 것이다.)

이는 확장과 수정에 대해 닫혀 있다고 볼 수 있다.

 

이를 해결하기 위해 의존성 역전이라는 개념을 추가로 적용할 필요가 있다.

이는 인터페이스 또는 추상 클래스를 이용하여 해결가능하다.

class AClass(){
    lateinit var actionClass : ActionClass

    fun test(){
        actionClass.action()
    }

}

class main(){
    fun startAsB(){
        val a  = AClass()
        a.actionClass = object :ActionClass{
            override fun action() {
                print("Action B!")
            }
        }
        a.test()
    }

    fun startAsC(){
        val a  = AClass()
        a.actionClass = object :ActionClass{
            override fun action() {
                print("Action C!")
            }
        }
        a.test()
    }
}

interface ActionClass {
    fun action()
}

 

위와 같이 인터페이스 ActionClass를 사용하여 action()이라는 메소드를 정의한다.

 

main 클래스에서 Action클래스의 action() 메소드를 필요에 따라 구현하여 AClass에 주입해 준다.

 

AClass에서는 action()이 어떤 행동을 하는지 알 수 없으며, 오로지 외부에서 구현한 행동을 실행하게 된다.

따라서, AClass는 별도로 수정하지 않아도, 외부에서 action()을 구현하여 주입해주기만 하면된다.

 

이를 수정과 확장에 열려있다고 한다.

 

위 구조를 통해 앞서 나열한 장점들을 얻을 수 있다.

  1. 재사용성을 높여준다.
    -> 상위 클래스에서 객체를 생성하여 하위 클래스로 넘겨주기 때문에 동일 레벨의 다른 하위클래스에도 해당 객체를 재사용할 수 있다.
  2. 테스트에 용이함
    -> 내부에서 인스턴스를 생성하는 것이 아닌 외부에서 주입받는 것으로 인터페이스만 맞다면 어떤 잘못된 코드를 넣어도 동작이 가능하여 Unit Test하기 용이해진다.
  3. 코드 가독성 증대
    -> 인터페이스 명세를 파악하면 어떤식으로 동작하는지 파악하기가 수월해진다.
  4. 코드 단순화
    -> 재사용성을 높여 보일러 플레이트 코드를 줄이고, 각 하위 레벨의 클래스에서 정의하는 코드 등이 단순해 진다.
  5. 결합도는 줄이고, 확장성과 유연성을 확보 가능
    -> 하위 레벨 클래스가 수정될 필요가 없어지며, 상위 레벨에서 다양한 방법으로 확장이 가능해지는 구조가 만들어 진다.

 

DI의 기본 개념에 대해서 정리해 보았다.

 

의존성 주입은 구조가 커지거나 조금 더 복잡한 구조의 프로젝트라면 구현하기 힘들어 지는 경향이 있다.

 

따라서 Android에는 DI를 도와주는 별도의 라이브러리가 존재한다.

 

Dagger, Hilt, Koin 등..

 

이는 별도로 정리하도록 한다.

 

'Android > DI' 카테고리의 다른 글

[Dagger2] Dagger2 기초  (0) 2021.07.23
Comments