본문 바로가기

IT/오브젝트

ch.09 유연한 설계

03. 의존성 주입

외부로 부터 독립적인 객체가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결하는 방법을 의존성 주입이라고한다.

 

  1. 생성자 주입 : 객체를 생성하는 시점에 생성자를 통한 의존성 해결
  2. setter 주입 : 객체 생성 후 setter 매서드를 통한 의존성 해결
  3. 메세드 주입 : 메서드 실행 시 인자를 이용한 의존성 해결

 

Movie movie = new Movie ("아바타", new 객체)

위와 같은 예시가 생성자 주입

movie.setName("아바타")

위와 같은 예시 setter 주입

 

movie.calculateDiscountAmount(screening, new AmountDiscountPolicy(....))

 

 위와 같은 예시가 메서드 주입

사실 위와 같은 메서드 의존성 주입의 한 종류로 볼 것인가에 대해서는 논란에 여지가 있다. 오브젝트 저자는 외부로부터 객체가 필요로 하는 의존성을 해결한다는 측면에서 의존성 주입의 한 종류로 간주한다고한다.

 

public interface DiscountPolicyInjectable {
 public void inject(DiscountPolicy discountPolicy)
}

public class Movie implements DiscountPolicyInjectable{
 private DiscountPolicy discountPolicy
 
 @Override
 public void inject(DiscountPolicy discountPolicy){
  this.discountPolicy = discountPolicy
 }
}

위와 같은 예시가 인터페이스 주입이다. 근본적으로는 setter 주입이나 프로퍼티 주입과 동일하다. 단지 어떤 대상을 어떻게 주입할 것인지를 인터페이스를 통해 명시적으로 선언한다는 차이만 있을뿐이다.

 

숨겨진 의존성은 나쁘다.

의존성 주입 외에도 의존성을 해결할 수 있는 다양한 방법이 존재한다. 그중에서 가장 널리 사용되는 대표적인 방법은 SERVICE LOCATOR 패턴이다. 서비스 로케이터는 의존성을 해결할 객체들을 보관하는 일종의 저장소이다.  

ServiceLocator.provide(new DiscountPolicy(...));

위와 같이 서비스 로케이터 패턴은 의존성을 해결 할 수있는 간단한 도구처럼 보인다. 하지만 이패턴에 가장 큰 단점은 의존성을 감춘다.

 

아래 예시를 보자

Movie movie = new Movie("아바타")

movie.calculateMovieFee(screnning);

개발자는 인스턴스에 모든 생성자를 넣었다. 하지만 calculateMovieFee를 호출하면 NullpointerException 예외가 던저진다. 서비스 로케이터에 할인 정책(DiscountPolicy)에 대한 의존성이 숨겨있기 떄문이다. 즉 개발자는 추가적으로 서비스 로케이터에 저장을 해줘야만 위에 메세지를 정확히 전달하여 해당 메서드가 처리할 수있게 된다.

 

의존성을 숨기는 코드는 단위 테스트 작성도 어렵다.

 

즉 문제의 원인은 숨겨진 의존성이 캡슐화를 위반했기 때문이다. 단순히 인스턴스 변수의 가시성을 private로 선언한다고 해서 캡슐화가 지켜지는 것은 아니다.

 

캡슐화는 코드를 읽고 이해하는 행위와 관련이 있다. 클래스의 퍼블릭 인터페이스만으로 사용 방법을 이해할수 있는 코드가 캡슐화의 관점에서 훌륭한 코드이다.

 

즉 숨겨진 의존성보다 명시적 의존성이 더 좋다. 가급적 의존성을 객체의 퍼블릭 인터페이스에 노출하라. 의존성을 구현 내부에 숨기면 숨길수록 코드를 읽기 어려워진다.