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

문법식·2022년 3월 2일
0

Effective Java 3/E

목록 보기
5/52

사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.

맞춤범 검사기는 사전에 의존한다. 맞춤법 검사기를 예로 설명하겠다.

//정적 유틸리티의 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker_StaticUtil {
    private static final  Lexicon dictionary=new KoreanDictionary();

    private SpellChecker_StaticUtil(){
        //Noninstantiable
    }

    public static boolean isValid(String word){
        throw new UnsupportedOperationException();
    }

    public static List<String> suggestions(String typo){
        throw new UnsupportedOperationException();
    }
}
//싱글톤을 잘못 사용한 예-유연하지 않고 테스트하기 어렵다.
public class SpellChecker_Singleton {
    private final Lexicon dictionary=new KoreanDictionary();
    private SpellChecker_Singleton(){

    }
    public static final SpellChecker_Singleton INSTANCE=new SpellChecker_Singleton(){

    };

    public boolean isValid(String word){
        throw new UnsupportedOperationException();
    }

    public List<String> suggestion(String typo){
        throw new UnsupportedOperationException();
    }
}

두 방식 모두 사전을 단 하나만 사용한다고 가정하는 점에서 좋은 코드가 아니다. 실전에서는 사전이 언어별로 따로 있고 특수 어휘용 사전을 별도로 두기도 한다. 사전 하나로 이 모든 쓰임에 대응할 수 없다. SpellChecker가 여러 사전을 사용할 수 있도록 만들려면 간단히 dictionary에서 final을 제거하고 다른 사전으로 교체하는 메서드를 추가하면 된다. 하지만 이 방식은 어색하고 오류를 내기 쉬우며 멀티 스레드에서는 쓸 수 없다. 왜냐하면 dictionaryfinal을 제거하고 교체 메서드를 추가하면 위의 stateless 였던 싱글톤이 stateful 싱글톤이 되는데 멀티 스레드 상태에서 stateful한 싱글톤은 스레드 세이프하지 않기 때문이다. 예를 들어 한 스레드는 위 맞춤범 검사 싱글톤을 한국어 사전으로 하여 사용하는데 다른 스레드는 맞춤범 검사 싱글톤의 사전을 영어 사전으로 바꿔버리고 하면 난장판이 되는 상황인 것이다.


클래스가 여러 자원 인스턴스를 지원해야 하며, 클라이언트가 원하는 자원을 사용해야 한다는 조건을 간단하게 만족시키는 패턴이 있는데, 바로 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식이다. 이는 의존 객체 주입의 한 형태로, 맞춤범 검사기를 생성할 때 의존 객체인 사전을 주입해주면 된다.

//적절한 구현
public class SpellChecker {
    private final Lexicon dictionary;
    
    public SpellChecker(Lexicon dictionary){
        this.dictionary=dictionary;
    }

	//생성자에 자원 팩터리를 넘겨주는 방식
    public SpellChecker(Supplier<Lexicon> dictionary){
        this.dictionary= Objects.requireNonNull(dictionary.get());
    }
  
    public boolean isValid(String word){
        throw new UnsupportedOperationException();
    }

    public List<String> suggestions(String typo){
        throw new UnsupportedOperationException();
    }
}

의존 객체 주입 패턴은 자원이 몇 개든 의존 관계가 어떻든 상관없이 잘 작동한다. 또한 불변을 보장하여 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있기도 하다. 의존 객체 주입은 생성자, 정적 팩터리(아이템 1), 빌더(아이템 2) 모두에 똑같이 응용할 수 있다.

이 패턴의 쓸만한 변형으로, 생성자에 자원 팩터리를 넘겨주는 방식이 있다.

의존 객체 주입은 유연성과 테스트 용이성을 개선해주긴 하지만, 의존성이 수천 개나 되는 큰 프로젝트에서는 코드를 어지럽게 만들기도 한다. 대거, 주스, 스프링 같은 의존 객체 주입 프레임워크를 사용하면 이런 어질러짐을 해소할 수 있다.

profile
백엔드

0개의 댓글