본문 바로가기
이펙티브 자바

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

by 에드박 2021. 4. 22.

보통 클래스는 하나 이상의 자원에 의존합니다.

예를들어 맞춤법 검사기는 사전(dictionary)에 의존하는데 이런 클래스를 정적 유틸리티 클래스 또는 싱글턴으로 구현하는 경우가 종종 있습니다. 두 방식 모두 사전을 단 한개만 사용한다고 가정한다는 점에서 재사용성이 떨어진다고 볼 수 있습니다.

 

package item5_20210422;

import java.util.ArrayList;
import java.util.List;

// 맞춤법 검사기 클래스는 사전(dictionary)에 의존
// 아래는 각각
// 유틸리티로 구현한 맞춤법 검사기
class SpellCheckTypeUtilClass {
    private static final Lexicon dictionary = new KoreanDictionary();

    private SpellCheckTypeUtilClass() {
    }

    public static boolean isValidHello(String word) {
        return word.equals(dictionary.hello());
    }

    public static List<String> suggestions(String typo) {
        return new ArrayList<>();
    }
}

// 싱글턴으로 구현한 맞춤법 검사기
class SpellCheckTypeSingleton {
    private final Lexicon dictionary = new EnglishDictionary();

    private SpellCheckTypeSingleton() {
    }

    private static final SpellCheckTypeSingleton INSTANCE = new SpellCheckTypeSingleton();

    public SpellCheckTypeSingleton getInstance() {
        return INSTANCE;
    }

    public boolean isValidHello(String word) {
        return word.equals(dictionary.hello());
    }

    public List<String> suggestions(String typo) {
        return new ArrayList<>();
    }
}

interface Lexicon {
    String hello();
}

class EnglishDictionary implements Lexicon {
    private final String HELLO = "hello";

    public String hello() {
        return HELLO;
    }
}

class KoreanDictionary implements Lexicon {
    private final String HELLO = "안녕하세요";

    public String hello() {
        return HELLO;
    }
}

 

클래스 파일에 직접 의존하는 자원을 명시했기 때문에 유연함이 떨어집니다.

만약 우리가 다른 언어(스페인어, 중국어, 일본어 등등)을 사용하려면 클래스를 새로 만들거나 클래스 파일을 변경해야할것입니다.

 

만약 여러 사전을 사용할 수 있도록 변경하고 싶을 때 간단하게 dictionary 필드에서 final 키워드를 제거하고

다른 사전으로 교체하는 메서드를 추가할 수 있습니다. 하지만 이 방식은 오류를 내기 쉬우며 멀티스레드 환경에서는 쓸 수 없습니다.

 

class SpellCheckTypeUtilClass {
    private Lexicon dictionary = new KoreanDictionary();

    private SpellCheckTypeUtilClass() {
    }

    // ...

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

// 싱글턴으로 구현한 맞춤법 검사기
class SpellCheckTypeSingleton {
    private Lexicon dictionary = new EnglishDictionary();

    private SpellCheckTypeSingleton() {
    }

    private static final SpellCheckTypeSingleton INSTANCE = new SpellCheckTypeSingleton();

    public SpellCheckTypeSingleton getInstance() {
        return INSTANCE;
    }

    // ...

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

 

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

 

클래스(SpellChecker)가 여러 자원 인스턴스를 지원해야 하며, 클라이언트가 원하는 자원을 사용할 수 있도록 해야합니다.

이 조건을 만족하는 간단한 패턴이 바로 의존 객체 주입 패턴 입니다.

 


의존 객체 주입 패턴

의존 객체 주입 패턴이란?

인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식을 의미합니다.

 

class SpellChecker {
    private final Lexicon dictionary;

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

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

 

 

위의 예제를 보면 생성자에서 dictionary 를 주입해주고 있습니다. 이미 많이 사용해왔겠지만 대부분 이 방식에 이름이 있는줄은 모르고 사용하고 있었습니다.

 

예제에서는 dictionary 라는 하나의 자원만 주입받고 있지만 자원이 몇개든 의존관계가 어떻든 상관없이 잘 동작합니다.

 

class SpellChecker2 {
    private final Lexicon dictionary;
    private final SpellRespository spellRespository;

    public SpellChecker2(Lexicon dictionary, SpellRespository spellRespository) {
        this.dictionary = dictionary;
        this.spellRespository = spellRespository;
    }

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

class SpellRespository {
}

 

또한 불변을 보장하여 같은 자원을 사용하는 클라이언트가 여럿이어도 의존 객체들을 안심하고 사용할 수 있습니다.

의존 객체 주입은 앞선 아이템인 정적 팩터리 메서드, 빌더 패턴 에서도 사용할 수 있습니다.

 

이 패턴의 쓸만한 변형으로 생성자에 자원 팩터리를 넘겨주는 방식이 있습니다. 즉, 팩터리 메서드 패턴(Factory Method pattern)을 구현한 것입니다.

 

자바 8에서 소개한 Supplier<T> 인터페이스가 팩터리를 표현한 완벽한 예입니다. Supplier<T>를 입력으로 받는 메서드는 일반적으로 한정적 와일드 카드 타입(bounded wildcard type, 아이템 31)을 사용해 팩터리 타입 매개변수를 제한해야 합니다.

 

이 방식을 사용해 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있습니다.

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

 

위의 코드는 제공된 팩터리가 생성한 타일(Tile)들로 구성된 모자이크를 만드는 create 메서드 입니다.

 

의존 객체 주입 패턴의 장점

  • 유연성
  • 불변성

의존 객체 주입의 단점과 해결 방법

 

의존 객체 주입이 유연성과 테스트 용이성을 개선해주긴 하지만, 의존성이 수천 개나 되는 큰 프로젝트에서는 코드를 어지럽게도 합니다.

이런 문제점은 대거(Dagger), 주스(Guice), 스프링(Spring) 같은 프레임워크를 사용하면 해소할 수 있습니다.

 

 

댓글