본문 바로가기

책리뷰/이펙티브자바

[이펙티브자바] 아이템5. 의존 객체 주입을 사용해라.

728x90
반응형

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

정적 유틸리티 클래스를 예로 들어볼 수 있다. 

 

정적유틸리티 잘못 사용한 예 - 유연하지 않고 테스트 하기 어렵다. 


public class SpellChecker {
    private static final Lexicon dictionary = ..;
    
    private SpellChecker() {}
    
    public static boolean isValid(String word) { ...}
    public static List<String> suggestions(String typo) { ...}
}

 

싱글턴을 잘못 사용한예 - 유연하지 않고 테스트 어렵다


public class SpellChecker {
    private static final Lexicon dictionary = ...;

    private SpellChecker() {}
    public static SpellChecker INSTANCE = new SpellChecker(...);

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

 

위 두 가지 방식 모두 사전을 단 하나만 사용한다고 가정한 점에서 유연하지 않은 방식이다. 

 

SpellChecker가 여러 사전을 사용할 수 있도록 만들려면 필드에서 final을 제거하고 다른 사전으로 교체하는 메서드를 추가해도 된다.

그러나 이 방식은 멀티스레드 환경에서 쓸 수 없다. 

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

 

인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 의존객체주입을 이용할 수 있다. 

 

의존 객체 주입 - 유연성과 테스트 용이성을 높여준다. 


public class SpellChecker {
    private final Lexicon dictionary;

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

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

}
public class Dictionary{
    public static void main(String[] args) {
            Lexicon dictionary = new KoreanDictionary();
            SpellChecker spellChecker = new SpellChecker(dictionary);
            SpellChecker.isValid("test");
        }
    }
}

interface Lexicon {}
class KoreanDictionary implements Lexicon { }

 

자원이 몇 개든 의존 관계가 어떻든 잘 동작하며 불변을 보장하기 때문에 여러 클라이언트가 안심하고 공유할 수 있다. 

이 패턴의 변형으로 생성자에 자원팩터리를 넘겨줄 수 있다. 

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

자바8의 Supplier<T> 인터페이스가 팩터리로 쓰기에 완벽하다. 

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

위와 같은 방식으로 자신이 명시한 하위타입이면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있다.

 

Supplier<T>


인자를 받지 않고 Type T 객체를 리턴하는 함수형 인터페이스 


public class SpellChecker {
    private final Lexicon dictionary;

    public SpellChecker(Supplier<Lexicon> dictionary) {
        this.dictionary = Objects.requireNonNull(dictionary.get());
    }

    public static boolean isValid(String word) {
        throw new UnsupportedOperationException();
    }
    public static List<String> suggestions(String typo) {
        throw new UnsupportedOperationException();
    }

    public static void main(String[] args) {
        Lexicon dictionary = new KoreanDictionary();
        SpellChecker spellChecker = new SpellChecker(() -> dictionary);
        spellChecker.isValid("test");

        
    }
}

 

클래스가 내부적으로 하나 이상의 자원에 의존하고 리소스에 따라 행동을 달리하는 클래스에는 싱글턴과 정적유틸리티 클래스를 사용하지 않는 것이 좋다. 클래스가 이 자원을 직접 만들게 해서도 안된다. 
필요한 자원을 생성자에 넘겨주는 의존객체주임을 이용해서 유연성, 재사용성, 테스트 용이성을 개선할 수 있다. 
728x90
반응형