싱글턴과 정적 유틸리티 클래스를 잘못 사용한 예
많은 클래스가 하나 이상의 자원에 의존하고 있는데, 사용하는 자원에 따라 동작이 달라지는 클래스에는 싱글턴과 정적 유틸리티 클래스 사용은 지양해야 한다.
예를 들어 사전에 의존하는 맞춤법 검사기를 예로 들어보자.
1. 정적 유틸리티를 잘못 사용한 예
public class SpellCheckerStatic {
private static final Dictionary dictionary = new Dictionary();
private SpellCheckerStatic() {}
...
}
2. 싱글톤을 잘못 사용한 예
public class SpellCheckerSingleton {
private static final Dictionary dictionary = new Dictionary();
public static SpellCheckerSingleton INSTANCE = new SpellCheckerSingleton();
private SpellCheckerSingleton() {};
}
위 코드의 문제점은 다음과 같다.
1. 테스트하기 어렵다
Dictionary를 spellCheck클래스 안에서 인스턴스화 하고 있기 때문에 원하는 테스트 객체를 사용할 수 없게 된다.
public class SpellCheckerSingleton {
// 여기서 KoreanDictionary()로 바꿀 수 없게 된다.
private static final Dictionary dictionary = new Dictionary();
}
2. 유연하지 않다.
Dictionary는 영어 사전, 한국어 사전, 프랑스어 사전 등 많은 사전들이 존재한다. 따라서 사전마다 새로운 클래스와 메소드들을 모두 다시 작성해야 한다.
public class KoreanDictionary { ... }
public class KoreanSpellChecker {
private final static KoreanDictionary dictinoary = new KoreanDictionary();
}
public class EnglishDictionary { ... }
public class EnglishSpellChecker {
private final static EnglishDictionary dictinoary = new EnglishSpellChecker();
}
final 필드를 제거하고 다른 사전으로 교체하는 메서드를 추가할 수 있지만, 이 방식은 멀티스레드 환경에서는 쓸 수 없다. 즉, 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.
의존 객체 주입
다음과 같이 Dictionary를 인터페이스로 만들고 사용하는 쪽에서 자원을 주입해주면 유연성이 증가한다.
public interface Dictionary {
boolean isValid(String word);
List<String> suggestions(String type);
}
public class SpellChecker {
private final Dictionary dictionary;
public SpellChecker(Dictionary dictionary) {
this.dictionary = dictionary;
}
public boolean isValid(String word) {
return dictionary.isValid(word);
}
public List<String> suggestions(String typo) {
return dictionary.suggestions(typo);
}
}
- dictionary.isValid(), dictionary.suggestions()를 호출하는 부분은 어떤 자원이 오더라도 달라지지 않는다. -> 유연성이 증가한다.
- 테스트를 할 때 원하는 객체를 자유롭게 선택할 수 있다.
SpellChecker spellChecker = new SpellChecker(new KoreanDictionary());
- Supplier를 이용하여 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있다.
public SpellChecker(Supplier<? extends Dictionary> dictionarySupplier) {
this.dictionary = dictionarySupplier.get();
}