Web/[JS] Common

Dependency Injection(의존성 주입)

ihl 2021. 5. 31. 00:32

1. Dependency

class A {
  constructor() {
    this.attr1 = 'a';
  }
}

class B {
  constructor() {
    this.a1 = new A();
    console.log(a1);
  }
}

  의존성(Dependency)란 두 모듈간의 연결을 의미한다. 위 코드의 B 클래스는 A 클래스를 포함하고 있다. 즉,  B를 만들기 위해서는 A가 필요하다. 이 때 B는 A에 의존한다고 말한다.

 

의존적인 Cat과 MyHome 클래스

  MyHome은 Cat 인스턴스를 생성하고 있으므로 MyHome은 Cat에 의존적인 클래스이다. 이 때 Cat 클래스 생성자에 파라미터가 추가되면 MyHome의 코드도 변경되어야 한다. MyHome과 Cat간에는 의존성이 있기 때문이다. 또한 MyHome에서 Cat 외의 다른 동물을 키우려면 어떻게 해야할까? 이 경우에도 MyHome의 코드를 변경해야한다. 

 

  위와 같은 의존성은 하나의 모듈이 변경되면 다른 모듈도 변경되어야 함을 의미한다. 따라서 코드에서 의존성은 최소화하는 것이 유지보수를 위해 좋다. MyHome에서 Cat에 대한 의존성을 없애려면 어떻게 해야할까? 이는 MyHome에서 어떤 동물을 키울 것이지, 어떤 동물의 cry()메소드를 호출할 것인지에 대한 관심사를 분리함으로써 해결할 수 있다.

 

2. Dependency Injection

Cat에 대한 의존성이 제거된 MyHome

  MyHome에서 Cat의 인스턴스를 구현하는 것이 아닌, 이미 생성된 Cat 인스턴스를 인자로 받는 코드로 변경하였다. 이 제 MyHome에서 Cat과 같은 다른 모듈을 import할 필요가 없다. 또한 Cat 외의 다른 동물을 키우기 위해 MyHome을 변경할 필요가 없어졌다. 이렇게 의존성을 분리하기 위해 외부에서 객체를 생성하여 넣어주는 것을 Dependency Injection이라고 한다. (정확히 말하자면 Dependency Injection 중 Constructor Injection라고 한다. 이 외에도 속성을 이용한 Setter Injection, 인터페이스를 이용한 Interfact Injection이 있다.)

 

  Dependency Injection으로 인해 각 모듈이 더 독립적이고 유연해졌다. 따라서 코드의 유지보수와 테스트에도 더 용이해지는 장점이 있다.

3. in TypeScript

MyHome in TS

  TypeScript에서 JavaScript처럼 Dependency Injection을 하면 오류가 생긴다. TS는 메소드의 파라미터에도 타입을 지정해주어야 하기 때문이다. MyHome 생성자에서 받을 파라미터의 타입은 무엇으로 지정해야할까?

 

animal의 타입은?

  가장 간단한 방법은 Cat클래스를 가져와서 지정하는 방법이다. 현재 코드에서 MyHome 인스턴스를 생성할 때 Cat 클래스를 사용하기 때문이다. 코드를 다시 살펴보자. 의존성이 제거되었는가? 여전히 MyHome를 구현하기 위해 Cat 모듈을 import하고 있으므로 의존성이 제거되었다고 볼 수 없다. 또한 Cat 외의 다른 동물로 변경하기 위해선 MyHome 생성자 함수의 코드를 변경해야하는 점도 여전하다.

 

3-1. 의존성 역전의 원리

  객체지향언어에서 MyHome을 Cat으로부터 독립시키기 위해 의존성 역전 원리(Dependency Inversion Principle)를 사용한다. 의존성 역전 원리란 상위 계층이 하위 계층에 의존하는 전통적인 의존관계를 반전시켜 상위 계층이 하위 계층의 구현으로부터 독립되게 만드는 것이다. 위의 MyHome과 Cat을 예로 들어보자.

 

 상위 계층은 하위 계층의 메소드를 호출함으로써 하위계층이 어떤 인자를 갖고 어떤 동작을 할지 정책을 결정한다. 즉, MyHome에 해당한다. 하위 계층은 동작의 세부내용을 정의하고 이를 메소드 형태로 추상화하여 제공한다. 즉 Cat에 해당한다. 

 

[의존성 역전의 원리]
1. 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위/하위 모듈이 모두 추상화에 의존해야 한다.
2. 추상화는 세부사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야한다.

  의존성 역전의 원리는 위와 같은 내용을 포함하고 있다. 즉, 의존성 역전의 원리에서 의존관계를 반전시키는 방법은 추상화이다. 추상화를 통해 구체적 의존 관계가 런타임에 동적으로 결정되므로 다형성을 활용할 수 있으며 모듈의 재사용성이 높아진다. TS에서 추상화의 방법은 인터페이스가 있다.

 

3-2. 인터페이스

  인터페이스는 서로 다른 두 개의 장치가 정보를 주고받기 위한 중간다리 역할의 매개체를 의미한다. 객체지향에서 인터페이스는 어떠한 코드에 접근할 수 있는 겉모습을 정의하며 이를 클래스가 implements 함으로써 구현한다. MyHome과 Cat에 인터페이스를 적용해보자.

 

Animal 인터페이스

  MyHome에서는 파라미터인 animal의 타입을 지정하기 위해 인터페이스가 필요하다. 따라서 MyHome을 기준으로 Animal이라는 인터페이스를 작성한다. MyHome은 animal의 cry() 메소드가 필요하므로 Animal 인터페이스에 cry()를 추가해준다. 즉, MyHome의 생성자의 인자 조건을 코드화 한 것이 Animal 인터페이스이다.

  MyHome 생성자에 인자로 들어갈 수 있는 Cat 클래스는 MyHome의 생성자의 인자로 주입될 수 있는 조건을 만족시켜야 한다. 즉, 인터페이스를 implements함으로써 이를 만족시킬 수 있다.

 

  결과 코드를 보면 MyHome과 Cat 모두 Animal이라는 인터페이스(추상화)에 있으며, MyHome과 Cat 클래스는 서로 아무런 관계가 없어보인다. 의존성이 분리된 것이다. 이처럼 JS에서는 타입과 인터페이스가 존재하지 않기 때문에 인스턴스를 주입(Dependency Injection)하는 것으로 충분하지만, TS에서는 인터페이스(Dependency Inversion Principle)를 통해 의존성 분리를 시켜주어야 한다.