"자원을 직접 명시하지 말고 의존 객체 주입을 사용하라"
대부분 클래스에서는 여러개의 자원에 의존한다. 여기서는 SpellChecker
(맞춤법 검사기)는 Dictionary
(사전)를 사용하고 이것을 의존성이라고 한다.
정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어려움
interface Lexicon {}
class KoreanDicationary implements Lexicon {}
public class SpellChecker {
private static final Lexicon dictionary = new KoreanDicationary(); // 한국어 사전
private SpellChecker() {} // 인스턴스를 가질 수 없다.
public static boolean isValid(String word) {...}
public static List<String> suggestions(String typo) {...}
}
싱글톤을 잘못 사용한 예 - 유연하지 않고 테스트하기 어려움
public class SpellChecker {
private final Lexicon dictionary = new KoreanDicationary();
private SpellChecker() {}
public static final SpellChecker INSTANCE = new SpellChecker(...);
public boolean isValid(String word) {...}
public List<String> suggestions(String typo) {...}
}
현실적으로 사전은 언어별로 여러개 존재하기 때문에 한가지의 사전만을 사용한다는 가정은 좋지 않다. 또한, 테스트시에는 테스트용 사전을 사용할 수도 있다.
위의 코드에서 final을 제거하고 사용한다면 다른 사전을 사용할 수 있지만 값이 변할 수 있기 때문에 멀티스레딩 환경에서는 적합하지 않다.
SpellChecker 클래스가 여러 자원 인스턴스를 지원해야 하고 클라이언트가 원하는 Dictionary를 사용하게끔 하면 된다. 즉, 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식이다.
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) {...}
}
기존과 달리 하나의 사전 타입에만 의존하지 않고, Lexicon의 구현체라면 주입받아 사용할 수 있으며 final 한정자를 사용하여 불변성을 띄고 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있다.
의존 객체 주입은 생성자, 정적 팩토리(아이템1), 빌더(아이템2)에도 적용 가능하다.
이 패턴의 변형으로 생성자에 자원 팩터리를 넘겨주는 방식이 있다.
이 예로, JAVA8의 Supplier<T>
인터페이스가 팩터리를 표현한 완벽한 예시이다. Supplier<T>
를 매개변수로 받는 메서드는 일반적으로 bounded wildcard type
(아이템31)으로 매개변수을 제한해야 한다.
// 클라이언트가 제공한 팩터리가 생성한 Tile로 구성된 Mosaic를 만드는 메서드
Mosaic create(Supplier<? extends Tile> tileFactory) {...}
의존 객체 주입이 유연성과 테스트 용이성을 향상시켜 주긴하지만 의존성이 많은 큰 프로젝트에서는 코드를 어지럽게 만들 수 있다. (Spring 같은 프레임워크를 사용하면 이런 단점을 해결할 수 있다.)
의존하는 자원에 따라 행동이 달라지는 클래스를 생성할 경우, 정적 유틸리티 클래스나 싱글톤은 사용하지 않는 것이 좋다. 대신, 리소스를 생성자, 팩토리로 전달하는 의존성 주입을 사용하여 클래스의 유연성, 재사용성, 테스트 용이성을 개선시킨다.