[이펙티브 자바] 아이템5 | 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

제롬·2022년 1월 5일
0

이펙티브자바

목록 보기
5/25

자원에 의존하는 클래스

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

많은 클래스가 하나 이상의 자원에 의존한다. 이런 클래스는 종종 정적 유틸리티 클래스로 구현된다.

[정적 유틸리티를 잘못 사용한 경우]

public class SpellChecker {
    private static final Lexicon dictionary = new KoreanDictionary(); // 의존성

    private SpellChecker() {
        // 객체 생성 방지
    }

    public static boolean isValid(String word){
        return true;
    }

    public static List<String> suggestions(String type){
        return null;
    }
}

위의 코드는 유연하지 않고 테스트하기가 어렵다. 비슷하게, 싱글턴으로 구현하는 경우도 많다.

[싱글턴을 잘못 사용한 경우]

public class SpellCheckerSingleTon {
    private final Lexicon dictionary = new KoreanDictionary();

    private SpellCheckerSingleTon() {
		// 객체 생성 방지
    }
    
    public static SpellCheckerSingleTon INSTANCE = new SpellCheckerSingleTon();

    public static boolean isValid(String word){
        return true;
    }

    public static List<String> suggestions(String type){
        return null;
    }

}

위의 두 방식은 모두 사전을 단 하나만 사용한다고 가정하는 점에서 적합하지 못하다. 사전은 언어별로 여러개의 사전이 있다. 이런 모든 사전을 역할을 하나의 사전으로 해결하는 것은 불가능하다.

여러개의 사전을 사용한다고 가정했을 때 final을 제거하고 dictionary를 변경하는 메서드를 추가할 수 있지만 오류가 발생할 가능성이 크고 멀티 스레드 환경에서 사용이 불가능하다.

[dictionary 교체 메서드 추가]

public class SpellChecker {
    private Lexicon dictionary = new KoreanDictionary();
    ...
    public static void changeDictionary() {
        dictionary = new EnglishDictionary();
    }
    ...
}

따라서 클래스 (SpellChecker) 가 여러 자원 인스턴스를 지원해야 하며, 클라이언트가 원하는 자원 (dictionary) 을 사용해야 한다. 즉, 의존성을 분리하여 자원을 외부로부터 주입받아야 한다.

인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식

의존 객체 주입의 한 형태

[의존 객체 주입 사용 경우]

public class SpellCheckerDi {
    private final Lexicon dictionary;

    public SpellCheckerDi(final Lexicon dictionary) {
        this.dictionary = Objects.requireNonNull(dictionary);
    } // 생성자를 이용해 외부로부터 자원(객체) 주입
      // 외부의 자원에 관계없이 객체를 생성할 수 있다.

    public static boolean isValid(String word){
        return true;
    }

    public static List<String> suggestions(String type){
        return null;
    }
}

의존 객체 주입 방식의 장점

  • 유연성과 테스트 용이성을 높여준다.
  • 자원의 개수와 의존 관계에 관계없이 잘 작동한다.
  • 불변을 보장하여 여러 클라이언트가 의존 객체를 안심하고 공유할 수 있다.
  • 의존 객체 주입은 생성자, 정적 팩터리, 빌더에 똑같이 응용가능하다.

이런 의존 객체 주입의 쓸만한 변형으로 생성자에 자원 팩터리를 넘겨주는 방법이 있다.

팩터리 메서드 패턴

팩터리란?
호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 말한다.

[Supplier<T> 인터페이스]

Spplier<T> 인터페이스는 팩터리 메서드를 표현한 완벽한 예시이다.

@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get(); // 매개변수가 없고 T를 반환하는 추상 메서드
}
  • Supplier<T> 를 입력받는 메서드는 일반적으로 한정적 와일드카드 타입을 사용해서 팩터리의 타입 매개변수를 제한해야 한다.
  • 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있다.
  • 의존 객체 주입은 큰 프로젝트에서는 코드를 어지럽게 만들기도 하므로 큰 프로젝트에서는 스프링같은 의존 객체 주입 프레임워크를 사용하자.

[클라이언트가 제공한 팩터리가 생성한 타일들로 구성된 모자이크를 만드는 메서드]

Mosaic create (Supplier<? extends Tile> tileFactory){
	...
}

[정리]
클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 정적 유틸리티 클래스와 싱글턴은 사용하지 않는것이 좋다. 대신 생성자에 필요한 자원을 넘겨주는 의존 객체 주입 기법을 사용하자.

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

0개의 댓글