[Effective Java] 아이템5 - 자원은 직접 명시하지 말고 의존 객체 주입을 사용하라
Note: 이 글은 Effective Java 책을 읽으면서 중요한 내용 및 알아야 하는 지식들을 정리한 글입니다.
또한, 백기선님의 Youtube 강의영상도 참고 했습니다.
대부분의 클래스는 하나 이상의 자원에 의존한다.
책에서는 Dictionary
에 의존하는 SpellChecker
를 예로 들고 있다. 이렇게 의존성을 가지고 있는 클래스를 다음과 같이 구현하는 경우가 있다.
정적 유틸리 클래스 [아이템4]
// 부적절한 static 유틸리티 사용 예 - 유연하지 않고 테스트 할 수 없다.
public class SpellChecker {
private static final Lexicon dictionary = new KoreanDicationry();
private SpellChecker() {
// Noninstantiable
}
public static boolean isValid(String word) {...}
public static List<String> suggestions(String typo) {...}
}
싱글턴 [아이템3]
// 부적절한 싱글톤 사용 예 - 유연하지 않고 테스트 할 수 없다.
public class SpellChecker {
private final Lexicon dictionary = new KoreanDicationry();
private SpellChecker() {}
public static final SpellChecker INSTANCE = new SpellChecker() {};
public boolean isValid(String word) {...}
public List<String> suggestions(String typo) {...}
}
위 두가지 방식 모두 Lexicon
을 다른 사전으로 변경하려면 코드를 수정해야 한다.
사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱클턴 방식은 적합하지 않다.
대신 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 의존 객체 주입 패턴을 사용한다.
의존성 주입 구현
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) { // 의존성 주입
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) {
throw new UnsupportedOperationException();
}
public List<String> suggestions(String typo) {
throw new UnsupportedOperationException();
}
}
class Lexicon {}
위와 같은 의존객체 주입은 생성자, 정적 팩터리아이템1, 빌더아이템2에도 응용할 수 있다.
팩터리 메서드 패턴 (Factorty Method Pattern)
의존 객체 주입의 변형으로, 생성자에 자원 팩터리를 넘겨주는 방식이 있다.
- 팩터리 : 호출할 때마다 특정 타입의 인스턴스를 만들어주는 객체
- 팩토리 메소드 패턴 : 서브클래스에서 구체적인 오브젝트 생성 방법을 경정하게 하는 것
자바8에서부터 사용가능한 Supplier<T>
인터페이스가 팩터리를 표현한 예이다.
// 유연하게 Lexicon 을 replace 할 수 있다.
Lexicon lexicon = new KoreanDictionary();
SpellChecker spellChecker = new SpellChecker(new Supplier<Lexicon>() {
@Override
public Lexicon get() {
return lexicon;
}
});
Lexicon testLexicon = new TestDictionary();
SpellChecker testChecker = new SpellChecker(() -> testLexicon); // 람다 표현식
Supplier<T>
인터페이스는 함수형 인터페이스이므로 람다 표현식으로 표현 가능하다.
This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. Interface Supplier API 명세서
한정적 와일드카드 타입(bounded wildcard type) 아이템31 를 사용해 팩터리의 타입 매개변수를 제한할 수도 있다.
Mosaic create(Supplier<? extends Tile> tileFactory){ ... }
의존성 주입은 스프링 프레임워크 (특히 토피의 스프링 프레임워크)에 익숙한 사람은 읽기 쉬운 주제이다.
Summary: 클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글턴, 정적 유틸리티 클래스로 구현하지 말고 의존성을 주입하자
의존성 객체를 활용한 예제
나이에 따라 검색어 자동완성 기능에 노출될 검색어를 다르게 하는 간단한 예제를 만들어 봤다.
- 19세 미만일 경우 “담요”, “술빵”, “와우”, “그날들” 노출
- 19세 이상일 경우에는 “담배”, “술”, “와인”, “그레이의 55가지 그림자” 노출
SpellChecker spellChecker = new SpellChecker(new Supplier<Lexicon>() {
@Override
public Lexicon get() {
if(age < 20) {
return new ChildDictionary();
} else {
return new GeneralDictionary();
}
}
});
return spellChecker.suggestions();
Supplier<T>
를 활용해서, 조건에 따라 객체를 return 하게 팩터리 메서드 패턴을 적용했다.
댓글남기기