본문 바로가기

Dev Book Review/Effective Java 3판

[Effective Java] Item5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

// 정적 유틸리티 클래스 방식
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 final Lexicon dictionary = ...;
    
    private SpellChecker() {}
    public static SpellChecker INSTANCE = new SpellChecker();
    
    public boolean isValid(String word) {...}
    public List<String> suggestions(String typo) {...}
}

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

예를 들어 예시 코드에서 구현한 맞춤법 검사기는 사전(dictionary)에 의존한다. 이럴 때는 일반적으로 정적 유틸리티 또는 싱글턴 방식으로 구현하는 경우가 많다. 하지만 해당 방식들은 유연하지 않고 테스트하기도 어렵다.

 

1) 사용할 사전이 단 하나라고 가정하고 있다. (예를 들어 국어사전, 영어사전 등이 필요하다면...? 일일이 바꿔주어야 한다)

2) 의존하는 객체를 직접 생성하고 있다. (위와 같은 내용이다)

 

그래서 적절히 사전(dictionary)을 바꿀 수 있게 setter 메서드를 추가해보자.

private Lexicon dictionary = ...; // 재할당을 위해 finaly 키워드 제거

public void setDictionary(Lexicon ditionary) {
        this.dictionary = dictionary;
    }

하지만 이 방법에도 문제가 있다.

1) 어색하고 오류를 내기 쉽다.

2) 멀티스레드 환경에서는 쓸 수 없다.

동시성 이슈가 발생할 수 있다.

해결책 - 의존 객체 주입

public class SpellChecker {
    private final Lexicon dictionary; // final 키워드를 통해 불변 보장

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

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

인스턴스를 생성할 때, 필요한 자원을 생성자에 넘겨준다!

이러한 의존 객체 주입은 다양한 장점이 존재한다.

 

1) 자원이 몇개든, 의존 관계가 어떻든 상관없이 잘 동작한다.

2) 불변을 보장하여 같은 자원을 사용하려는 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있다.

인스턴스 변수로 선언된 의존 객체(Lexicon dictionary;)는 메모리의 Heap 영역에 생성된다.
따라서 해당 의존 객체는 여러 클라이언트(스레드)에 공유되는 자원이다.
만약 해당 객체가 불변이 아니라면, 스레드는 의존 객체에 접근하여 상태를 바꿀 수 있으며 동시성 이슈가 발생한다.

마찬가지로 생성자에 자원 팩터리를 넘겨주는 방식도 있다.

여기서 팩터리란, 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 말한다. = Supplier<? extends Title>

Mosaic starMosaic = mosaicCreator.create(() -> new Tile("별무늬"));
Mosaic heartMosaic = mosaicCreator.create(() -> new Tile("하트무늬"));
public Mosaic create(Supplier<? extends Tile> tileFactory) {
    Tile tile1 = tileFactory.get();
    Tile tile2 = tileFactory.get();
    Tile tile3 = tileFactory.get();
    Tile tile4 = tileFactory.get();
    return new Mosaic(Arrays.asList(tile1, tile2, tile3, tile4));
}