1. 개요
많은 클래스는 하나 이상의 자원에 의존한다.
예를 들어 맞춤법 검사기는 사전에 의존한다.
이런 클래스를 정적 유틸리티 클래스로 구현한 모습을 드물지 않게 볼 수 있는데, 이런 경우는 유연하지 않고 테스트 하기 어렵다.
참고) 정적유틸 클래스란?
정적 유틸리티 클래스는 인스턴스를 생성하지 않고 정적 메서드만으로 기능을 제공하는 클래스.
- 모든 메서드가 static
- 생성자를 private으로 막아서 인스턴스 생성 방지
- 상태(인스턴스 변수)를 가지지 않음
2. 잘못된 접근법들
2-1. 정적 유틸리티 클래스 사용
코드 5-1 정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // 객체 생성 방지
public static boolean isValid(String word) { ... }
public static List<String> suggestions(String typo) { ... }
}
2-2. 싱글턴 사용
코드 5-2 싱글턴을 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker {
private final Lexicon dictionary = ...;
private SpellChecker(...) {}
public static SpellChecker INSTANCE = new SpellChecker(...);
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
싱글턴으로 사전을 구현한 경우에도 마찬가지다
3. 문제점 분석
실제 맞춤법 검사기는 여러 개의 클래스가 사전을 참고한다
여러 사전을 사용할 수 있도록 만들어보면 어떨까?
우선 dictionary 필드에서 final을 제거하고, 다른 dictionary로 교체할 수 있는 메서드도 추가할 수 있지만
이 방식은 오류 나기도 쉽고, 멀티 쓰레드 환경에선 사용 불가하다.
여러 스레드가 동시에 사전을 바꾸고 사용하면 어떤 사전이 적용될지 예측할 수 없기 때문에 일관성 보장이 안되기 때문이다.
사용하는 자원에 따라 동작이 달라지는 클래스엔 정적 유틸리티 클래스나 싱글턴 방식이 부적합하다.
4. 해결책: 의존 객체 주입
그래서 SpellChecker 클래스가 여러 자원 인스턴스를 지원해야 하며,
클라이언트가 원하는 자원(dictionary)을 지원해야 한다.
이를 간단히 구현할 수 있는 패턴이 있다.
바로 인스턴스 생성 시 생성자에게 필요한 자원을 넘겨주는 것이다.
보통 우리가 생성자로 객체를 생성할 때 매개변수를 넘겨서 객체를 생성하는 방식이라고 이해하면 될 거 같다.
4-1. 올바른 구현
코드 5-3 의존 객체 주입은 유연성과 테스트 용이성을 높여준다.
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
4-2. 의존 객체 주입의 장점
위 코드에서 보면 의존 객체 주입을 하게 되면 dictionary 필드에 final을 붙일 수 있어 불변의 특성을 띈다.
그래서 같은 자원(dictionary)을 안전하게 공유할 수 있게 된다.
4-3. 실무에서의 고려사항
의존 객체 주입이 유연성과 테스트 용이성을 높여주긴 하지만,
의존성이 수천개나 되는 프로젝트에선 코드를 어지럽게 한다.
이 때 대거(Dagger), 주스(Guice), 스프링(Spring)과 같은 의존 객체 주입 프레임워크를 사용하면 이러한 문제를 해결할 수 있습니다.
5. 정리
- 클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.
- 이 자원들을 클래스가 직접 만들게 해서도 안 된다
- 대신 필요한 자원을 생성자에 넘겨주자.
- 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 기가 막히게 개선해준다.
'JAVA > Effective JAVA 3E' 카테고리의 다른 글
| [Effective Java 3E] - 6. 불필요한 객체 생성을 피하라 (0) | 2025.09.06 |
|---|---|
| [Effective JAVA 3E] - 4. 인스턴스화을 막으려거든 private 생성자를 사용하라 (0) | 2025.08.31 |
| [Effective JAVA 3E] - 2. 생성자에 매개변수가 많다면 빌더를 고려하라 (2) | 2025.08.26 |