많은 클래스가 하나 이상의 자원에 의존한다. 사전 클래스에 의존하는 맞춤법 검사기 클래스를 살펴보자.
public class SpellChecker {
private static final Lexicon dictionary = new EnglishDictionary();
private SpellChecker() {} // 객체 생성 방지
public static boolean isValid(String word) {...}
public static List<String> suggestions(String typo) {...}
}
public class SpellChecker {
private final LexiconDictionary dictionary = new EnglishDictionary();
private SpellChecker(...) {}
public static SpellChecker INSTANCE = new SpellChecker(...);
public boolean isValid(String word) {...}
public List<String> suggestions(String typo) {...}
}
첫 번째 클래스는 정적 유틸리티 클래스다. 두 번재는 싱글턴 클래스다.
두 방식 모두 검사기 클래스가 필요한 자원인 사전을 직접 명시하고 있다. 즉, 단 하나의 사전만을 사용한다고 가정한 것이다. 그러나, 실제론 맞춤법 검사기는 언어별로 사전이 따로 있고 테스트를 위한 사전도 필요하게 될 수 있다.
사전의 final
키워드를 빼고 세터 메서드를 통해 사전을 교체하는 방법이 있긴하다. 그러나 이 방법은 어색하고 오류가 나기 쉽다. 무엇보다 멀티스레드 환경에서는 사용할 수 없다. 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.
자원을 직접 명시하는 방법보다 더 좋은 방법이 있다. 인스턴스를 생성할 때 필요한 자원을 넘겨주는 의존 객체 주입 방식이다.
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) {...}
public List<String> suggestions(String typo) {...}
}
의존 객체 주입을 사용하면 자원을 명시하는 책임이 클래스 내부에서 외부로 바뀐다. 즉, 사용자가 원하는 사전을 사용하는 맞춤법 검사기를 얼마든지 만들 수 있게 된다. 또한 불변성(아이템 17)이 보장된다. 의존 객체 주입 방식은 정적 팩터리, 빌더에도 똑같이 응용할 수 있다.
의존객체 주입을 자원 팩터리를 넘겨줄 수도 있다. 팩터리란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 말한다. 자바8의 Supplier<T>
인터페이스가 팩터리의 예다. Supplier<T>
를 매개변수로 받는 메서드는 한정적 와일드 카드(아이템 31)를 통해 타입 매개변수를 제한해야 한다. 예를 살펴보자.
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
create 메서드는 Tile 타입을 제공하는 팩터리 객체를 주입 받아서 모자이크 객체를 만든다.
의존 객체 주입을 사용하면 유연성, 재사용성, 테스트 용이성을 개선해준다. 그러나 의존성이 많아질 수록 코드를 어지럽게 만들 수 있다. 스프링 같은 의존 객체 주입 프레임워크는 의존성 주입을 적극 활용하면서도 코드를 어지럽게 만들지 않을 수 있도록 도와준다.