SpellChecker 와 Dictionary로 예를 들어보자.
유연하지 않은 구현
public class SpellChecker{
private static final Lexicon dictionary = new KoreanDictionary();
private SpellChecker(){
}
public static boolean isValid(String word){
throw new UnsupportedOperationException();
}
public static List<String> suggestions(String typo){
throw new UnsupportedOperationException();
}
public static void main(String[] args){
SpellChecker.isValid("hello");
}
}
interface Lexicon{}
class KoreanDictionary implements Lexicon{}
public class SpellChecker{
private final Lexicon dictionary = new KoreanDictionary();
private SpellChecker(){
}
public static final SpellChecker INSTANCE = new SpellChecker(){
};
public boolean isValid(String word){
throw new UnsupportedOperationException();
}
public List<String> suggestions(String typo){
throw new UnsupportedOperationException();
}
}
Dictionary가 하나로 고정되어 유연하지 않은 코드가 생성된다.
어떤 클래스가 사용하는 리소스에 따라 행동을 달리 해야 하는 경우에는 스태틱 유틸리티 클래스와 싱글톤을 사용하는 것은 부적절하다.
public class SpellChecker{
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary){
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word){
throw new UnsupportedOperationException();
}
public List<String> suggestions(String typo){
throw new UnsupportedOperationException();
}
}
class Lexicon{}
의존성 주입은 생성자, 스태틱 팩토리, 빌더에도 적용할 수 있다.
Supplier 인터페이스로 리소스의 팩토리를 생성자에 전달하는 방법도 있다.
public class SpellChecker{
private final Lexicon dictionary;
public SpellChecker(Supplier<Lexicon> dictionary){
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word){
throw new UnsupportedOperationException();
}
public List<String> suggestions(String typo){
throw new UnsupportedOperationException();
}
public static void main(String[] args){
Lexicon lexicon = new KoreanDictionary();
SpellChecker spellChecker = new SpellChecker(()->new lexicon);
}
}
interface Lexicon{}
class KoreanDictionary implements Lexicon{}