아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

Bobby·2022년 5월 22일
0

이펙티브 자바

목록 보기
5/7
post-thumbnail

다른 자원에 의존하는 클래스

많은 클래스가 하나 이상의 자원에 의존한다.

다음과 같은 맞춤법 검사기 클래스(SpellChecker)는 Dictionary 클래스에 의존한다.

  1. 정적 유틸리티 클래스로 구현
public class SpellChecker {

    private static final Dictionary dictionary = new DefaultDictionary();

    private SpellChecker() {
    }

    public static boolean isValid(String word) {
    	...
    }

    public static List<String> suggestions(String typo) {
    	...
    }
}
  1. 싱글톤 클래스로 구현
public class SpellChecker {

    private final Dictionary dictionary = new DefaultDictionary();

    private SpellChecker() {}

    public static final SpellChecker INSTANCE = new SpellChecker();

    public boolean isValid(String word) {
    	...
    }

    public List<String> suggestions(String typo) 
    	...
    }
}
  • 두 가지 방식 모두 DefaultDictionary 클래스를 직접 의존하고 있다. 언어별로 다른 사전을 두거나 특수 어휘용 사전을 별도로 두거나 또는 테스트용 사전이 필요할 수도 있다. 그런 경우에는 많은 코드의 수정이 필요하게 될 것이다.

SpellChecker 클래스가 다양한 사전을 사용할 수 있도록 변경하려면 어떻게 할까?

  • 간단히 사전의 final키워드를 삭제하고 사전 클래스를 교체할 수 있는 메소드를 추가하는 방법이 있다.
  • 하지만 이 방법은 멀티스레드 환경에서는 사용 할 수 없다.
  • 이처럼 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다는 것을 알 수 있다.

의존 객체 주입 패턴

인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식이다.
스프링을 사용해 봤다면 익숙한 방식일 것이다.
의존 객체 주입은 생성자, 정적 팩토리, 빌더 모두에 똑같이 응용할 수 있다.

  • 다형성 사용을 위해 사전 인터페이스를 정의한다.
public interface Dictionary {

    boolean isValid(String word);
    List<String> suggestions(String typo);
}
public class SpellChecker {

    private final Dictionary dictionary;

    public SpellChecker(Dictionary dictionary) {
        this.dictionary = dictionary;
    }

    public boolean isValid(String word) {
        return dictionary.isValid();
    }

    public List<String> suggestions(String typo) {
        return dictionary.suggestions();
    }
}
  • EnglishDictionanry, KoreanDictionary .. 등등 여러가지 사전 클래스가 있다고 가정하면 클래스를 생성하는 시점에 필요한 사전을 주입하는 형태로 사용한다.
	SpellChecker english = new SpellChecker(new EnglishDictionanry());
    SpellChecker korean = new SpellChecker(new KoreanDictionary());
    
    english.isValid("test");
    korean.isValid("테스트");
  • SpellChecker 클래스 내부에는 각각 영어 사전과 한국어 사전이 주입 되어 사용된다.
  • 이렇게 의존 객체 주입을 사용하면 새로운 사전이 추가 되더라도 기존 코드의 수정없이 새로운 사전을 사용시에 주입만 해주면 쉽게 사용할 수 있다.

의존 객체 주입 패턴 응용

  • 팩토리 메소드 패턴을 구현하여 생성자에 팩토리 클래스를 넘겨주는 방식이 있다.

  • Dictionary를 리턴해 주는 DictionaryFactory 클래스

public interface DictionaryFactory  {

    Dictionary getDictionary();
}
public class EnglishDictionaryFactory implements DictionaryFactory {
    @Override
    public Dictionary getDictionary() {
        return new EnglishDictionary();
    }
}
public class KoreanDictionaryFactory implements DictionaryFactory {
    @Override
    public Dictionary getDictionary() {
        return new KoreanDictionary();
    }
}
public class SpellChecker {

    private final Dictionary dictionary;

    public SpellChecker(DictionaryFactory dictionaryFactory) {
        this.dictionary = dictionaryFactory.getDictionary();
    }

    public boolean isValid(String word) {
        return dictionary.isValid();
    }

    public List<String> suggestions(String typo) {
        return dictionary.suggestions();
    }
}
	DictionaryFactory dictionaryFactory = new KoreanDictionaryFactory();
	SpellChecker spellChecker = new SpellChecker(dictionaryFactory);
    
    spellChecker.isValid("테스트");
  • Dictionary 를 구현한 객체를 직접 전달해 줄 수도 있지만 팩토리 메소드를 전달할 때 이점이 있다.
  • 많은 곳에서 KoreanDictionary가 사용된다고 생각해보자.
  • 만약 KoreanDictionaryV2 로 변경 되었다면 new KoreanDictionary() 로 객체를 생성했던 부분 모두 변경해야 한다.
  • 팩토리 메소드의 경우에는 해당 팩토리만 변경하면 된다.
public class KoreanDictionaryFactory implements DictionaryFactory {
    @Override
    public Dictionary getDictionary() {
        return new KoreanDictionaryV2();
    }
}

핵심 정리

  • 클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.
  • 이 자원들을 클래스가 직접 만들게 해서도 안된다.
  • 대신 필요한 자원을 (혹은 그 자원을 만들어주는 팩토리를) 생성자에 (혹은 정적 팩터리나 빌더에) 넘겨주자.
  • 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선해준다.
profile
물흐르듯 개발하다 대박나기

0개의 댓글