의존 관계를 가지는 두 가지 경우를 먼저 살펴보자
// 정적 유틸리티 클래스 방식
public class SpellChecker {
private static final Lexicon dictionary = new KoreaDictionary();
private SpellChecker()
{} // 객체 생성 방지
}
// 싱글톤 방식
public class SpellChecker {
private static final Lexicon dictionary = new KoreaDictionary();
public static SpellChecker INSTANCE = new SpellChecker();
private SpellChecker(){};
}
위의 두가지 경우는 다양한 사전 종류에 유연하게 대처하지 못한다. 만약 KoreaDictionary말고 EnglishDictionary를 사용하려면 클라이언트 코드 자체를 변경해야 한다.
또한 사전을 변경하는 코드를 집어넣는다면 멀티쓰레드 환경에서 쓸 수 없다.
이유는 위의 코드에서 사전은 공유 데이터의 형태로 설정되어있다. 유틸리티 클래스나 싱글톤이나 여러 사용자들이 하나의 객체를 공유해서 사용하는 것이기 때문에 중간에 누군가 사전을 변경한다면 장애가 일어나는 쪽이 생길 것이다.
이를 해결할 수 있는 방법으로 인스턴스 생성시 생성자에 필요한 자원을 넘겨주는 의존 객체 주입 기법이 있다.
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary){
this.dictionary = Objects.requireNonNull(dictionary);
}
}
위의 코드를 살펴보면 dictionary 변수는 생성자가 호출되며 매개변수로 들어온 dictionary로 초기화가 된다. 우리가 스프링을 공부할때 가장 기초가 되는 DI(Dependency Injection)과 같다고 할 수 있다.
클라이언트는 그냥 생성자로 들어온 사전을 사용하기만 하면 되고, 멤버 변수인 dictionary는 final로 설정되어 있기 때문에 새로운 참조값을 받을 수 없다. 즉 불변의 상태가 유지된다! 따라서 중간에 dictionary가 바뀔 염려는 전혀 하지 않아도 된다.
여기서 중요한건 SpellChecker 생성자의 매개변수인 dictionary의 타입이 구체 클래스가 아닌 인터페이스여야 의미가 있다는 것이다.
만약 의존관계가 구체타입으로 주입이 된다면, 우리는 사전 종류를 바꾸기 위해 결국 클라이언트 코드를 변경해야 한다.
인터페이스로 들어왔을때 클라이언트는 dictionary의 종류에 상관없이 내부 로직을 진행 할 수 있을 것이다.
이는 객체지향의 원칙 중 다형성을 구현한 것이라 할 수 있다.
[ 팩토리 메서드 패턴과 Supplier ]
팩토리 메서드 패턴(Factory Method Pattern)은 팩토리를 추상화 한 후, 주입하는 구체 팩토리에 따라서 다른 프로덕트를 반환하는 패턴이다. 이를 사용하면 의존관계 주입 시에 클라이언트 코드 변경 없이 유연한 변경이 가능하다.
java8의 Supplier<T>가 팩토리 메서드 패턴을 잘 구현해놓았다. 아래의 코드로 확인해보자.
// DefaultDictionary의 모든 하위타입을 반환할 수 있는 dictionaryFactory들을 받는다.
public SpellChecker(Supplier<? extends DefaultDictionary> dictionaryFactory)
{
this.dictionary = dictionaryFactory.get();
}
public static void main(String[] args) {
// 람다를 통해서 DefaultDictionary 하위 타입의 인스턴스를 뭐든 넣어주면 된다.
SpellChecker spellChecker = new SpellChecker(() -> new DefaultDictionary());
}
💡 핵심
싱글톤이나 정적 유틸리티 클래스를 사용하면 테스트하기 어렵고 멀테 쓰레드 환경에서 불리하다.
의존 객체 주입과 다형성을 이용해서 클라이언트 코드의 변경 없이 객체들을 조립할 수 있게 만들자.