Kim ByeungHyun
[Effective Java] 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 본문
클래스 들이 다른 클래스에 의존하는 경우가 있다.
예로 SpellChecker라는 클래스에서 dictionary라는 유틸리티 클래스르 사용한다고 가정해보겠다.
1. 정적 유틸리티 클래스
// 사용은 이렇게
SpellChecker.isValid(word)
2. 싱글턴
// 사용은 이렇게
SpellChecker.INSTANCE.isValid(word)
두 방법 모두 확장에 유연하지 않고 테스트가 어렵다.
사전은 굉장히 여러 종류가 있는데(한국어 사전, 영어 사전, 특수 어휘용 사전등) dictionary 하나로만 이 역할을 모두 수행하기에는 어렵고, SpellCheacker는 dictionary 하나만 사용할 수 있기 때문이다.
사용하는 자원에 따라 동작이 달라지는 클래스는 위 두 방법이 적합하지 않다
final을 삭제하고 사전을 교체하는 메소드를 적용해보자.
public class SpellChecker {
private static Lexicon dictionary = ...;
public static void changeDictionary(Lexicon new) {
dictionary = new;
}
}
//사용은 이렇게!
SpellChecker.changeDictionary(newDictionary);
→ 어색하고 멀티스레드 환경에서는 사용 할 수 없다.
대신 클래스(SpellChecker)가 여러 자원 인스턴스를 지원해야하며, 클라이언트가 원하는 자원(dictionary)을 사용해야 한다.
이 조건을 만족하는 간단한 패턴
3. 의존 객체 주입의 형태
이 방법은 인스턴스를 생성할 때 생성자에게 필요한 자원을 넘겨주는 방식이다.
- 코드를 보면서 이해
@Require....
public class SpellChecker {
private final Lexicon dictionary;
// 여기서 의존성 주입을!
@Autowired
public SpellChecker(Lexicon dictionary){
this.dictionary = Objects.requireNotNull(dictionary);
}
public static boolean isVaild(String word) {...}
public static List<String> suggestions(String typo) {...}
}
// 인터페이스
interface Lexicon {}
// Lexicon을 상속 받아서 구현
public class myDictionary implements Lexicon {
...
}
// 사용은 이렇게!
Lexicon dic = new myDictionary();
SpellChecker chk = new SpellChecker(dic);
chk.isVaild(word);
이처럼 의존성 주입은 유연성과 테스트 용이성을 개선해주지만, 의존성이 너무 많아지면 코드가 장황해질수도 있다.
대거(Dagger), 주스(Guice), 스프링(Spring) 같은 프레임워크를 사용하면 이런 어질러짐을 해소할 수 있다.
핵심
클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스를 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다. 자원들을 클래스가 직접 만들게 해서도 안된다. 대신 필요한 자원을 생성자에 넘겨주자. 의존 객체 주입이라 하는 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선해준다.
예로 SpellChecker라는 클래스에서 dictionary라는 유틸리티 클래스르 사용한다고 가정해보겠다.
1. 정적 유틸리티 클래스
// 사용은 이렇게
SpellChecker.isValid(word)
2. 싱글턴
// 사용은 이렇게
SpellChecker.INSTANCE.isValid(word)
두 방법 모두 확장에 유연하지 않고 테스트가 어렵다.
사전은 굉장히 여러 종류가 있는데(한국어 사전, 영어 사전, 특수 어휘용 사전등) dictionary 하나로만 이 역할을 모두 수행하기에는 어렵고, SpellCheacker는 dictionary 하나만 사용할 수 있기 때문이다.
사용하는 자원에 따라 동작이 달라지는 클래스는 위 두 방법이 적합하지 않다
final을 삭제하고 사전을 교체하는 메소드를 적용해보자.
public class SpellChecker {
private static Lexicon dictionary = ...;
public static void changeDictionary(Lexicon new) {
dictionary = new;
}
}
//사용은 이렇게!
SpellChecker.changeDictionary(newDictionary);
→ 어색하고 멀티스레드 환경에서는 사용 할 수 없다.
대신 클래스(SpellChecker)가 여러 자원 인스턴스를 지원해야하며, 클라이언트가 원하는 자원(dictionary)을 사용해야 한다.
이 조건을 만족하는 간단한 패턴
3. 의존 객체 주입의 형태
이 방법은 인스턴스를 생성할 때 생성자에게 필요한 자원을 넘겨주는 방식이다.
- 코드를 보면서 이해
@Require....
public class SpellChecker {
private final Lexicon dictionary;
// 여기서 의존성 주입을!
@Autowired
public SpellChecker(Lexicon dictionary){
this.dictionary = Objects.requireNotNull(dictionary);
}
public static boolean isVaild(String word) {...}
public static List<String> suggestions(String typo) {...}
}
// 인터페이스
interface Lexicon {}
// Lexicon을 상속 받아서 구현
public class myDictionary implements Lexicon {
...
}
// 사용은 이렇게!
Lexicon dic = new myDictionary();
SpellChecker chk = new SpellChecker(dic);
chk.isVaild(word);
이처럼 의존성 주입은 유연성과 테스트 용이성을 개선해주지만, 의존성이 너무 많아지면 코드가 장황해질수도 있다.
대거(Dagger), 주스(Guice), 스프링(Spring) 같은 프레임워크를 사용하면 이런 어질러짐을 해소할 수 있다.
핵심
클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스를 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다. 자원들을 클래스가 직접 만들게 해서도 안된다. 대신 필요한 자원을 생성자에 넘겨주자. 의존 객체 주입이라 하는 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선해준다.