클래스들이 자원(다른 클래스)에 의존하는 경우가 있다.
예를 들어서 SpellChecker라는 클래스에서 dictionary라는 유틸리티 클래스를 사용한다고 가정해보겠다.
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // 인스턴스화 방지 (아이템 4 참고)
public static boolean isVaild(String word) {...}
public static List<String> suggestions(String typo) {...}
}
//사용은 이렇게!
SpellChecker.isValid(word);
public class SpellChecker {
private final Lexicon dictionary = ...;
private SpellChecker() {} // 인스턴스화 방지 (아이템 4 참고)
public static SpellChecker INSTANCE = new SpellChecker(...);
public static boolean isVaild(String word) {...}
public static List<String> suggestions(String typo) {...}
}
//사용은 이렇게!
SpellChecker.INSTANCE.isValid(word);
두 방법 모두 확장에 유연하지 않고 테스트가 어렵다.
사전은 굉장히 여러 종류가 있는데(한국어 사전, 영어 사전, 특수 어휘용 사전 등...)
dictionary 하나로만 이 역할을 모두 수행하기에는 어렵고,
SpellChecker는 dictionary 하나만 사용할 수 있기 때문이다.
사용하는 자원에 따라 동작이 달라지는 클래스는 위 두 방법이 적합하지 않다.
그렇다면 아래와 같이 final을 삭제하고 사전을 교체하는 메소드를 작성하는 것은 어떨까?
public class SpellChecker {
private static Lexicon dictionary = ...;
...
public static void changeDictionary(Lexicon new) {
dictionary = new;
}
...
}
//사용은 이렇게!
SpellChecker.changeDictionary(newDictionary);
-> 어색하고 멀티스레드 환경에서는 사용할 수 없다.
이 방법은 인스턴스를 생성할 때 생성자에게 필요한 자원을 넘겨주는 방식이다.
코드를 보는게 더 이해하기 편하다.
public class SpellChecker {
private final Lexicon dictionary;
// 여기서 의존성 주입을!
public SpellChecker(Lexicon dictionary){
this.dictionary = Objects.requireNotNull(dictionary);
}
public static boolean isVaild(String word) {...}
public static List<String> suggestions(String typo) {...}
}
// 인터페이스
interface Lexicon {}
// Lexicon을 상속 받아서 구현
public class myDictionary implements Lexicon {
...
}
// 사용은 이렇게!
Lexicon dic = new myDictionary();
SpellChecker chk = new SpellChecker(dic);
chk.isVaild(word);
이 패턴의 응용 방법으로는 생성자에 자원 팩토리를 넘겨주는 방식이 있다. (팩토리 메소드 패턴)
ex. Supplier
이처럼 의존성 주입은 유연성과 테스트 용이성을 개선해주지만, 의존성이 너무 많아지면 코드가 장황해질 수도 있다.