보통 클래스는 하나 이상의 자원에 의존합니다.
예를들어 맞춤법 검사기는 사전(dictionary)에 의존하는데 이런 클래스를 정적 유틸리티 클래스 또는 싱글턴으로 구현하는 경우가 종종 있습니다. 두 방식 모두 사전을 단 한개만 사용한다고 가정한다는 점에서 재사용성이 떨어진다고 볼 수 있습니다.
package item5_20210422;
import java.util.ArrayList;
import java.util.List;
// 맞춤법 검사기 클래스는 사전(dictionary)에 의존
// 아래는 각각
// 유틸리티로 구현한 맞춤법 검사기
class SpellCheckTypeUtilClass {
private static final Lexicon dictionary = new KoreanDictionary();
private SpellCheckTypeUtilClass() {
}
public static boolean isValidHello(String word) {
return word.equals(dictionary.hello());
}
public static List<String> suggestions(String typo) {
return new ArrayList<>();
}
}
// 싱글턴으로 구현한 맞춤법 검사기
class SpellCheckTypeSingleton {
private final Lexicon dictionary = new EnglishDictionary();
private SpellCheckTypeSingleton() {
}
private static final SpellCheckTypeSingleton INSTANCE = new SpellCheckTypeSingleton();
public SpellCheckTypeSingleton getInstance() {
return INSTANCE;
}
public boolean isValidHello(String word) {
return word.equals(dictionary.hello());
}
public List<String> suggestions(String typo) {
return new ArrayList<>();
}
}
interface Lexicon {
String hello();
}
class EnglishDictionary implements Lexicon {
private final String HELLO = "hello";
public String hello() {
return HELLO;
}
}
class KoreanDictionary implements Lexicon {
private final String HELLO = "안녕하세요";
public String hello() {
return HELLO;
}
}
클래스 파일에 직접 의존하는 자원을 명시했기 때문에 유연함이 떨어집니다.
만약 우리가 다른 언어(스페인어, 중국어, 일본어 등등)을 사용하려면 클래스를 새로 만들거나 클래스 파일을 변경해야할것입니다.
만약 여러 사전을 사용할 수 있도록 변경하고 싶을 때 간단하게 dictionary 필드에서 final 키워드를 제거하고
다른 사전으로 교체하는 메서드를 추가할 수 있습니다. 하지만 이 방식은 오류를 내기 쉬우며 멀티스레드 환경에서는 쓸 수 없습니다.
class SpellCheckTypeUtilClass {
private Lexicon dictionary = new KoreanDictionary();
private SpellCheckTypeUtilClass() {
}
// ...
public void changeDictionary(Lexicon dictionary) {
this.dictionary = dictionary;
}
}
// 싱글턴으로 구현한 맞춤법 검사기
class SpellCheckTypeSingleton {
private Lexicon dictionary = new EnglishDictionary();
private SpellCheckTypeSingleton() {
}
private static final SpellCheckTypeSingleton INSTANCE = new SpellCheckTypeSingleton();
public SpellCheckTypeSingleton getInstance() {
return INSTANCE;
}
// ...
public void changeDictionary(Lexicon dictionary) {
this.dictionary = dictionary;
}
}
사용하는 자원에 따라 동작이 달리지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 어울리지 않습니다.
클래스(SpellChecker)가 여러 자원 인스턴스를 지원해야 하며, 클라이언트가 원하는 자원을 사용할 수 있도록 해야합니다.
이 조건을 만족하는 간단한 패턴이 바로 의존 객체 주입 패턴 입니다.
의존 객체 주입 패턴
의존 객체 주입 패턴이란?
인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식을 의미합니다.
class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = dictionary;
}
public boolean isValid(String word) {
return true;
}
}
위의 예제를 보면 생성자에서 dictionary 를 주입해주고 있습니다. 이미 많이 사용해왔겠지만 대부분 이 방식에 이름이 있는줄은 모르고 사용하고 있었습니다.
예제에서는 dictionary 라는 하나의 자원만 주입받고 있지만 자원이 몇개든 의존관계가 어떻든 상관없이 잘 동작합니다.
class SpellChecker2 {
private final Lexicon dictionary;
private final SpellRespository spellRespository;
public SpellChecker2(Lexicon dictionary, SpellRespository spellRespository) {
this.dictionary = dictionary;
this.spellRespository = spellRespository;
}
public boolean isValid(String word) {
return true;
}
}
class SpellRespository {
}
또한 불변을 보장하여 같은 자원을 사용하는 클라이언트가 여럿이어도 의존 객체들을 안심하고 사용할 수 있습니다.
의존 객체 주입은 앞선 아이템인 정적 팩터리 메서드, 빌더 패턴 에서도 사용할 수 있습니다.
이 패턴의 쓸만한 변형으로 생성자에 자원 팩터리를 넘겨주는 방식이 있습니다. 즉, 팩터리 메서드 패턴(Factory Method pattern)을 구현한 것입니다.
자바 8에서 소개한 Supplier<T> 인터페이스가 팩터리를 표현한 완벽한 예입니다. Supplier<T>를 입력으로 받는 메서드는 일반적으로 한정적 와일드 카드 타입(bounded wildcard type, 아이템 31)을 사용해 팩터리 타입 매개변수를 제한해야 합니다.
이 방식을 사용해 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있습니다.
Mosaic create(Supplier<? extends Tile> tileFactory) {...}
위의 코드는 제공된 팩터리가 생성한 타일(Tile)들로 구성된 모자이크를 만드는 create 메서드 입니다.
의존 객체 주입 패턴의 장점
- 유연성
- 불변성
의존 객체 주입의 단점과 해결 방법
의존 객체 주입이 유연성과 테스트 용이성을 개선해주긴 하지만, 의존성이 수천 개나 되는 큰 프로젝트에서는 코드를 어지럽게도 합니다.
이런 문제점은 대거(Dagger), 주스(Guice), 스프링(Spring) 같은 프레임워크를 사용하면 해소할 수 있습니다.
'이펙티브 자바' 카테고리의 다른 글
[아이템 7] 다 쓴 객체 참조를 해제하라 (0) | 2021.04.24 |
---|---|
[아이템 6] 불필요한 객체 생성을 피하라 (0) | 2021.04.22 |
[아이템 4] 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2021.04.20 |
[아이템 3] private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2021.04.20 |
[아이템 2] 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2021.04.18 |
댓글