많은 클래스가 하나 이상의 자원에 의존한다.
이런 클래스는 정적 유틸리티 클래스
로 구현하거나 싱글턴
으로 구현할 수 있는데, 둘 다 그리 좋지 않은 방법이다.
다음의 예시를 보자.
// 정적 유틸리티
public calss SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // 객체 생성 방지
public static boolean isValid(String word) {...}
public static List<String> suggestions(String typo) {...}
}
// 싱글턴
public calss SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {}
private static SpellChecker INSTANCE = new SpellChecker(...);
public static boolean isValid(String word) {...}
public static List<String> suggestions(String typo) {...}
}
두 방식 모두 dictionary를 단 하나 사용한다고 가정한다는 점에서 좋아 보이지 않는다.
실전에서는 사전이 언어별로 따로 있고, 특수 어휘용 사전을 별두로 두기도 한다.
또한 테스트용 사전도 따로 필요할 수 있다.
이런 방식은 유연하지 않고, 테스트하기도 어렵다.
즉, 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.
대신에 의족 객체 주입
을 사용하자. 이를 위한 방법 3가지를 알아보자.
대신에 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식
을 사용하면 클래스가 여러 자원 인스턴스를 지원할 수 있다. 이는 의존 객체 주입
의 한 형태이다.
위의 패턴의 쓸만한 변형으로는, 생성자에 자원 팩터리를 넘겨주는 방식
이 있다.
팩터리
란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 말한다.
즉,팩터리 메서드 패턴
을 구현한 것이다.
의존 객체 주입은 유연성과 테스트 용이성을 개선해주기는 하지만, 의존성이 수천 개나 되는 큰 프로젝트에서는 오히려 코드를 어지럽게 만들 수도 있다.
이럴 때는 대거(Dagger), 주스(Guice), 스프링(Spring)과 같은 의존 객체 주입 프레임워크
를 이용하는 것이 좋다.
📌 핵심 정리
- 클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.
- 이 자원들을 클래스가 직접 만들게 해서도 안 된다.
- 대신 필요한 자원을 (혹은 그 자원을 만들어주는 팩터리를) 생성자 (혹은 정적 팩터리나 빌더에) 넘겨주자.
- 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선해준다.