리소스를 엮을 때는 의존성 주입을 선호하라
- 대부분의 클래스는 여러 리소스에 의존하게 된다.
- 이 책에서는 SpellChecker / Dictionary를 예로 들고있다.
- 즉 SpellCheck가 Dictionary를 사용하고 이를 의존하는 리소스 또는 의존성 이라고 부른다.
- 이때 SpellChecker를 다음과 같이 구현하는 경우가 있다.
public class SpellChecker { // 바꿔끼기가 힘들다. 테스트도 힘들다. private static final Lexicon dictionary = new KoreanDictionary(); private SpellChecker() { // Noninstantiable } public static boolean isValid(String word) { throw new UnsupportedOperationException(); } public static List<String> suggestions(String type) { 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 type) { throw new UnsupportedOperationException(); } // 싱글톤 인스턴스를 통해서 사용한다. public static void main(String[] args) { SpellChecker.INSTANCE.isValid("hello"); } } interface Lexicon{} class KoreanDictionary implements Lexicon { }
- 사전을 하나만 사용할거라면 위와 같은 구현도 만족스러울 수 있겠지만 실제로는 각 언어의 맞춤법 검사기는 사용하는 사전이 각기 다르다.
- 또한 테스트코드에서는 테스트용 사전을 사용하고 싶을 수도 있다.
- 어떤 클래스가 사용하는 리소스에 따라 활동을 달리 해야하는 경우에는 스테틱 유틸리티 클래스와 싱글톤을 사용하는 것은 적절하지 못하다.
- 그런 요구사항을 만족시킬 수 있는 간단한 패턴으로 생성자를 사용해서 새 인스턴스를 생성할 때 사용할 리소스를 넘겨주는 방식이 있다.
적절한 구현
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 type) { throw new UnsupportedOperationException(); } public static void main(String[] args) { Lexicon lexicon = new KoreanDictionary(); SpellChecker spellChecker = new SpellChecker(lexicon); spellChecker.isValid("hello"); } } interface Lexicon { } class KoreanDictionary implements Lexicon { }
- 위와 같은 의존성 주입은 생성자, 스테틱 팩토리 그리고 빌더에도 적용할 수 있다.
- 이 패턴의 변종으로 리소스의 팩토리를 생성자에 전달하는 방법도 있다. 이 방법은 자바 8에 들어온 Supplier
인터페이스가 그런 팩토리로 쓰기에 완벽하다.
- Supplier를 인자로 받는 메서드는 보통 bounded wildcard type으로 입력을 제한해야 한다. > Mosaic create(Supplier extends Tile) tileFactory) { … }
- 의존성 주입이 유연함과 테스트 용이함을 크게 향상시켜 주지만 의존성이 많은 큰 프로젝트인 경우에는 코드가 장황해 질 수 있다.
- 그점은 대거, 쥬스, 스프링같은 프레임웤을 사용해서 해결할 수 있다.
요약
- 의존하는 리소스에 따라 행동을 달리하는 클래스를 만들 때는 싱글톤이나 스테틱 유틸 클래스를 사용하지 말자. 그런 경우에는 리소스를 생성자나 팩토리로 전달하는 의존성 주입을 사용하여 유연함, 재사용성, 테스트 용이성을 향상시키자.