의존성 주입

kante·2020년 11월 30일
0

2020 11 27

2020 11 30 업데이트

개발을 하다 보면 외부 라이브러리를 사용해야 할 때가 수도 없이 많이 생긴다. 하지만 그 라이브러리가 Deprecated 되거나, 다른 라이브러리로 교체해야 하는 일이 생기면 어떻게 될까? 극단적으로 코드 처음부터 하나하나 뜯어 고쳐야 할 수도 있다.

이럴 경우, 외부 라이브러리를 가지고, 외부에서 호출할 수 있게 인터페이스를 제공해주는 DI, 즉 Dependency Injection(의존성 주입)하는 것을 만드는 것이다.


개요

Javascript나 JAVA 등여러 다른 언어에서 의존성 분리를 사용하지만 최근에 배웠던 Swift언어로 의존성 분리를 공부해보았다.

  • 의존성 : 함수에 필요한 클래스 또는 참조변수나 객체에 의존하는 것
class A{
    var anum: Int = 1
}
class B{
    var bNum = A()
}

let b = B()
print(b.bNum.aNum)

B 클래스에서 A클래스를 내부에 변수로 사용하게 되므로

B 클래스는 A 클래스에 의존관계가 생기게 된다.

  • 주입 : 외부에서 객체를 생성해서 넣어주는 것. coupling 낮춤
class B{
    var bNum: Int
    init(num : Int){
        self.bNum = num
    }
    func setBNum(num: Int){
        self.bNum = num
    }
}

let b = B(Int(3)) // 외부에서 객체 생성
print(b.bNum)

b.setBNum(Int(5)) // 객체 생성

Int(3), Int(5)를 생성해서 함수로 넣어줌.

-> 이렇게 객체를 외부에서 생성해서 클래스 안에 넣어주는 것을 주입 한다고 말한다.

강한 결합

  • 객체 내부에서 다른 객체를 생성할 때.
  • A 내부에서 B 객체를 직접 생성한다면, B 객체를 C 객체로 바꾸고 싶은 경우 A 클래스도 수정해야 함.

느슨한 결합

  • 객체를 주입 받는다는 것 -> 생성된 객체를 인터페이스를 통해 넘겨받는 것.
  • 런타임시에 의존관계가 결정되기 때문에 유연해짐
  • SOLID 원칙 중 O에 해당하는 Open Closed Principle -> 생성자 주입을 사용

의존성 주입

내부에서 만든 변수를 외부에서 넣어주면 된다.

의존성 주입을 지키기 위한 3가지 조건

  1. 클래스 모델이나 코드에 런타임 시점의 의존관계가 드러나지 않는다.
  2. 런타임 시점의 의존관계는 제 3의 존재(Factory 등)에 의해 결정되어야 한다
  3. 의존 관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로써 만들어진다.
class A{
    var aNum: Int = 1
}
class B{
    var bNum: A
    init(num: A){
        self.bNum = num
    }
}
let b = B(A())

B 클래스 내부변수로 A 클래스를 사용하고, 이 A 클래스를 외부에서 생성해서 B에 주입해준다.


의존성 분리

하지만 위의 코드를 의존성 주입이라고 말하지는 않는다.

첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다. 

상위 모듈이 하위 모듈에 의존해서는 안되기 때문에, 이것을 반전시킨다. 대부분의 언어는 interface이고 Swift는 protocol을 사용한다.

protocol DIInterface: AnyObject{
    var num: Int{get set}
}
class A: DIInterface{
    var aNum = 1
}
class B{
    var bNum: DIInterface
    init(num: DIInterface){
        self.bNum = num
    }
}

let b = B(A())
  • 의존 관계를 독립시킬 DIInterface protocol을 선언한다.
  • 그 다음, DIInterface에 의존관계가 있는 A 클래스를 선언한다.
  • A와 의존관계가 있는 클래스를 선언한다.
  • 외부에서 A클래스를 B 클래스에 주입한다.
  • 이렇게 의존 관계를 독립시킬 수 있다.

IOC(Inversion OF Control) : 제어의 역전

프로그래머가 작성한 프로그램이, 라이브러리의 흐름 제어를 받게 되는 디자인 패턴

  • 프로그램 제어권을 Framework가 가져가는 것.
  • 개발자는 비즈니스 로직에만 신경쓰면 됨.
  • 개발자가 annotation, xml 등을 통해 설정을 해 놓으면 Container가 알아서 처리
  • SOLID 원칙 중 S를 지킬 수 있게 됨.
블랙박스 프레임워크
  • 상속을 통해서가 아닌, 인터페이스 프로토콜에 따라 사용자가 정의한 메소드를 프레임워크의 동작을 변경하는데 사용함.

  • 자바 코드의 블랙박스 프레임워크

    new Thread(new Runnable() {
    	@Override
        public void run() {
        	// Do something
        }
    }).start();
    • Thread 클래스는 블랙박스 프레임워크
    • Runnable은 인터페이스.
    • run은 사용자가 Override하여 정의한 메소드.
    • 이것을 IoC라고 한다.

IoC는 사용자가 구현한 인터페이스를 의존하는 하위 모듈로 참조하든 말든 상관하지 않는다. 하지만 DI는 의존관계에 대한 것이기 때문에 하위 모듈은 상위 모듈의 has 관계가 되어야 한다.

IoC Container

  • IoC를 구현하는 프레임워크로 객체를 관리, 생성을 책임지고 의존성을 관리하는 컨테이너

  • 모든 의존성을 Container를 통해 받아오게 됨

    https://javaslave.tistory.com/11

    에서 Hellow World 를 통한

    1. 메시지 render(출력)하는 기능과 메시지를 provider(제공) 하는 기능 분리 (의존성감소)
    2. render와 provider를 유연하게(소스코드 수정 없이) 변경가능 하여야 함 (IoC and DI)

    요구조건 만족을 위한 좋은 설명이 있어서 넣어보았다.


장점

  1. 코드 재사용성 증가 : 서비스가 한 번만 구현하여 여러 클라이언트에게 의존성을 주입해주어 효과적이다.
  2. 리팩토링 과정 수월 : 간접적으로 의존성 주입하는 모듈이 분리되어 있기 때문에 어떤 내용을 바꾸어야 한다면, 그 부분만 바꾸면 된다.
  3. 보일러플레이트 코드 감소 : 반복적으로 비슷한 형태를 띄는 코드가 감소된다.

참고

  1. https://medium.com/@jang.wangsu/di-dependency-injection-%EC%9D%B4%EB%9E%80-1b12fdefec4f
  2. https://charlezz.medium.com/%EB%B3%B4%EC%9D%BC%EB%9F%AC%ED%94%8C%EB%A0%88%EC%9D%B4%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%9E%80-boilerplate-code-83009a8d3297
  3. https://juyeop.tistory.com/26
  4. https://velog.io/@sudhez_a/DI-IoC-%EC%A0%95%EB%A6%AC
profile
경험많은 개발자가 되자

0개의 댓글