[Effective Java] 2장 객체 생성과 파괴 - 아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

배상규·2023년 9월 15일
0

이펙티브 자바

목록 보기
5/12
post-thumbnail

의존성 주입 기법

클래스가 하나 이상의 자원에 의존하고, 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.이 자원들을 클래스가 직접 만들게 해서도 안 된다. 대신 필요한 자원을 생성자에 넘겨주자. 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선해준다.


예제

정적 유틸리티를 잘못 사용한 예

public class SpellChecker {

    private static final Lexicon dictionary = new Lexicon();

    private SpellChecker() {
    }

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

    public static List<String> suggestions(String typo) {
      ....
    }
}

해당 유틸리티 클래스는 사전을 하나만 사용한다고 가정하고 이 있다. 하지만 실제에서는 사전이 언어별 따로 있고 특수 어휘용 사전을 따로 두는 경우도 있다.

싱글턴을 잘못 사용한 예

public class SpellChecker {

    private final Lexicon dictionary = new Lexicon();

    public static SpellChecker INSTANCE = new SpellChecker();

    private SpellChecker() {
    }

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

    public static List<String> suggestions(String typo) {
        ...
    }
}

싱글턴 방식에서도 마찬가지 사전을 하나만 사용한다고 가정하기에 앞선 단점이 발생한다.


해결책

final 한정자를 제거

public class SpellChecker {

    private Lexicon dictionary = new Lexicon();

    public static SpellChecker INSTANCE = new SpellChecker();

    private SpellChecker() {
    }

    public static void changeDictionary(Lexicon dictionary) {
        this.dictionary = dictionary;
    }

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

    public static List<String> suggestions(String typo) {
        ...
    }
}

dictionary 필드에서 final 한정자를 제거하고 다른 사전으로 교체하는 메서드를 추가하였다. 하지만 이방식은 오류를 내기 쉬우며 멀티스레드 환경에서는 사용할 수 없다.

의존 객체 주입 방식

public class SpellChecker {

    private final Lexicon dictionary;

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

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

    public static List<String> suggestions(String typo) {
        ...
    }
}

의존 객체 주입을 사용한 코드는 위에 예는 처럼 dictionary라는 하나의 자원만 사용하지만, 자원이 몇 개가되든 잘 작동한다. 또한 불변을 보장하여 여러 클라이언트가 의존 객체들을 공유할 수 있는 장점이 있다. 의존 객체 중비은 생성자, 정적 팩터리, 빌더 모두 똑같이 응용이 가능하다.

의존 객체 주입은 생성자에 자원 팩터리를 넘겨주는 방식도 종종 사용된다. 팩터리란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어 주는 객체를 말한다. 이러한 방식을 팩터리 메서드 패턴이라고 한다.자바 8에서 Supplier< T >가 완벽한 예시이다.

public static List<Test> create(Supplier<? extends Test> generator) {
    ...
}

일반적으로 한정적 와일드카드 타입을 사용하여 팩터리의 타입 매개 변수를 제한한다. 이 방식을 사용해 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있다.

의존 객체 주입은 유연성과 테스트 용이성을 개선해주지만, 의존성이 수천 개나 되는 큰 프로젝트에서는 코드가 상당히 방대해지고 어지러워 진다. 이런 경우 의존 객체 주입 프레임워크(Dagger, Guice, Spring)을 사용하여 이러한 문제를 해결할 수 있다.

profile
기록에 성장을

0개의 댓글