사용하는 자원에 따라 동작이 달라지는 클래스는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.
의존 객체 주입이란 인스턴스를 생성할 때 필요한 자원을 넘겨주는 방식이다.
"자원을 직접 명시하지 말고"의 의미는 자원을 직접 생성한다는 의미이다.
이 방식의 변형으로 생성자에 자원 팩터리를 넘겨줄 수 있다.
의존 객체 주입을 사용하면 클래스의 유연성, 재사용성, 테스트 용이성을 개선할 수 있다.
예시 코드
Before
SpellChecker는 정적 유틸리티 클래스이며, 이 기능을 활용하기 위해서는 DictIonary를 사용해야한다.
isValid() 테스트를 작성하고 싶은 경우, Dictionary를 사용할 수 밖에 없다. (만약 Dictionary 생성 비용이 매우 비싸다면 테스트가 어려울 것이다.)
테스트에서는 진짜 Dictionary가 아니라 mock한 가짜 Dictionary를 사용하고 싶어도 그렇게 할 수 없다. (물론 static한 것도 mocking 할 수 있으나 객체지향적이라 보기 어렵다.)
유연성과 재사용성도 떨어진다. (다양한 구현체를 끼워넣을 수 없기 때문이다.)
public class SpellChecker {
private static final Dictionary dictionary = new DefaultDictionary();
private SpellChecker() {}
public static boolean isValid(String word) {
// TODO 여기 SpellChecker 코드
return dictionary.contains(word);
}
public static List<String> suggestions(String typo) {
// TODO 여기 SpellChecker 코드
return dictionary.closeWordsTo(typo);
}
}
After
Dictionary가 interface라면 주입하는 방식으로 사용할 수 있다.
public class SpellChecker {
private final Dictionary dictionary;
public SpellChecker(Dictionary dictionary) {
this.dictionary = dictionary;
}
public boolean isValid(String word) {
// TODO 여기 SpellChecker 코드
return dictionary.contains(word);
}
public List<String> suggestions(String typo) {
// TODO 여기 SpellChecker 코드
return dictionary.closeWordsTo(typo);
}
}
테스트 또한 내부가 아니라 외부에서 SpellChecker가 사용하는 resource를 주입해줄 수 있게 되어, 값비싼 연산 없이 테스트를 할 수 있게 된다.
SpellChecker의 기능을 얼마든지 사용하면서도, 원하는 구현체로 테스트를 진행할 수 있다.
class SpellCheckerTest {
@Test
void isValid() {
SpellChecker spellChecker = new SpellChecker(new MockDictionaty());
spellChecker.isValid("test");
}
}
완벽 공략
p29, 이 패턴의 쓸만한 변형으로 생성자에 자원 팩터리를 넘겨주는 방식이 있다.
p29, 자바 8에서 소개한 Supplier 인터페이스가 팩터리를 표현한 완벽한 예다.
p29, 한정적 와일드카드 타입을 사용해 팩터리의 타입 매개변수를 제한해야 한다.
p29, 팩터리 메소드 패턴
p30, 의존 객체가 많은 경우에 Dagger, Guice, 스프링 같은 의존 객체 주입 프레 임워크 도입을 고려할 수 있다.
예시 코드 (p.29)
Supplie를 사용한 예이다.
"한정적 와일드카드 타입을 사용" 는 코드에 주석으로 명시해두었다.
구체적인 타입을 사용한 Supplier를 의미하는 것으로 판단.
인터페이스를 사용한다면 굳이 이렇게 사용할 필요가 없을 것으로 생각되기 때문이다.
public class SpellChecker {
private final Dictionary dictionary;
public SpellChecker(Dictionary dictionary) {
this.dictionary = dictionary;
}
// public SpellChecker(Supplier<? extends Dictionary> dictionarySupplier) {
public SpellChecker(Supplier dictionarySupplier) {
this.dictionary = dictionarySupplier.get();
}
...
}
``` java
class SpellCheckerTest {
@Test
void isValid() {
SpellChecker spellChecker = new SpellChecker(MockDictionary::new);
spellChecker.isValid("test");
}
}
스프링 IoC
BeanFactory 또는 ApplicationContext
Inversion of Control - 뒤짚힌 제어권
자기 코드에 대한 제어권을 자기 자신이 가지고 있지 않고 위부에서 제어하는 경우.
제어권? 인스턴스를 만들거나, 어떤 메소드를 실행하거나, 필요로하는 의존성을 주입 받는 등...
아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
핵심 정리
예시 코드
Before
isValid()
테스트를 작성하고 싶은 경우, Dictionary를 사용할 수 밖에 없다. (만약 Dictionary 생성 비용이 매우 비싸다면 테스트가 어려울 것이다.)After
Dictionary가 interface라면 주입하는 방식으로 사용할 수 있다.
완벽 공략
예시 코드 (p.29)
"한정적 와일드카드 타입을 사용" 는 코드에 주석으로 명시해두었다.
private final Dictionary dictionary;
public SpellChecker(Dictionary dictionary) { this.dictionary = dictionary; } // public SpellChecker(Supplier<? extends Dictionary> dictionarySupplier) { public SpellChecker(Supplier dictionarySupplier) {
this.dictionary = dictionarySupplier.get();
}
... }
스프링 IoC
BeanFactory 또는 ApplicationContext