많은 클래스가 하나 이상의 자원에 의존한다. 가령, '맞춤법 검사기'는 '사전'에 의존하고 있다.
public class SpellChecker {
private static final Lexion dictionary = ...;
private SpellChecker() {} // 객체 생성 방지용
public static boolean isValid() {...}
public static List<String> suggestions(String typo) {...}
}
public class SpellChecker {
private static final Lexion dictionary = ...;
private SpellChecker(...) {}
public static SpellChecker INSTANCE = new SpellChecker(...);
public static boolean isValid() {...}
public static List<String> suggestions(String typo) {...}
}
위 두 방식은 모두 '사전을 단 하나만 사용한다'고 가정한다는 점에서 아쉬움이 있다. 사전 하나로 모든 쓰임에 대응하기에는 힘들 수 있기 때문!
실전에서는 사전이 언어별로 따로 있고, 특수 어휘용 사전을 별도로 두기도 하기 때문이다. 심지어는 테스트용 사전도 필요할 수 있다.
시도 가능한 방법
: dictionary
필드에서 final
한정자를 제거하고 다른 사전으로 교체하는 메서드를 추가하기
👉🏻 결론
사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않음
: 의존관계 주입, 의존성 주입
"A가 B를 의존한다"는 의미는 무엇일까? 의존 관계에 대해 토비의 스프링에서는 다음과 같이 정의하고 있다.
의존 대상 B가 변하면, 그것이 A에 영향을 미친다.
- 이일민, 토비의 스프링 3.1 中 -
의존성 주입의 한 형태
클래스(ex: SpellChecker)가 여러 자원 인스턴스를 지원할 수 있으며, 클라이언트가 원하는 자원을 사용해야 함
많은 프로그래머가 이 방식에 이름이 있다는 사실도 모른 채 사용해왔음
: 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주기(주입하기)
public class SpellChecker {
private final Lexion dictionary;
public SpellChecker(Lexion dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) {...}
public List<String> suggestions(String typo) {...}
}
👉🏻 효과
- 유연성과 테스트 용이성을 높여줌
- 위에서는 dictionary라는 딱 하나의 자원만 사용하지만, 자원의 개수와 의존관계 형태에 상관 없이 잘 작동함
- 불변을 보장(private, final 등 이용)하여 같은 자원을 사용하려는 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있음
- 의존 객체 주입은 생성자 뿐만 아니라 정적 팩터리, 빌더 모두에 똑같이 응용할 수 있음
: 생성자에 자원 팩터리를 넘겨주는 방식
Factory : 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체
즉, Factory Method Pattern을 구현하는 것
ex: Supplier<T>
인터페이스가 팩터리를 표현한 완벽한 예시
Supplier<T>
를 입력 받는 메서드는 일반적으로 한정적인 와일드카드타입을 사용해 팩터리의 타입 매개변수를 제한해야 함이 방식을 사용해 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있음
클라이언트가 제공한 팩터리가 생성한 타일(Tile)들로 구성된 모자이크(Mosaic)를 만드는 메서드
Mosaic create(Supplier<? extends Tile> tileFactory) {...}
👉🏻 해결책
- Dagger(대거)
- Guice(주스)
- Spring(스프링)
등의 의존 객체 주입 프레임워크를 사용하면 이러한 어질러짐을 해소할 수 있음
* 의존 객체를 주입하도록 설계된 API를 알맞게 응용해 사용하고 있음
⛔ 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.
⛔ 이 자원들을 클래스가 직접 만들게 해서도 안 된다.
✅ 필요한 자원 (혹은 그 자원을 만들어주는 factory)을 생성자(혹은 정적 팩터리, 빌더)에 넘겨주자.
✅ 의존 객체 주입이라 칭하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 크게 개선해준다.
참고자료
Effective Java, Joshua Bloch
https://tecoble.techcourse.co.kr/post/2021-04-27-dependency-injection/