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

2022. 1. 4. 00:03·dev./Effective-Java
Effective Java 3판 아이템5 의존 객체 주입에 관한 내용이다. 책에서 소개한 SpellChecker 라는 클래스를 사용한다.

 

| 하나의 클래스가 하나 이상의 자원에 의존하는 경우

 

[ 정적 유틸리티 클래스 구현 (아이템4) ]

 

맞춤법 검사기 (SpellChecker)는 사전(Dictionary)에 의존하고 있는 객체의 관계를 아래 예제와 같이 정적 유틸리티 클래스로 구현할 수 있다.

public class SpellChecker {
    // dictionary 객체가 고정됨 -> 변경이 어렵다.
    private static final Lexicon dictionary = new KoreanDictionary();

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

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

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

    public static void main(String[] args) {
        boolean isChecked = SpellChecker.isValid("hello");
        System.out.println("isChecked = " + isChecked);
    }
}

interface Lexicon {}

class KoreanDictionary implements Lexicon {

}

class EnglishDictionary implements Lexicon {

}

 

위 코드를 보면 KoreanDictionary 객체로 고정되어 있기 때문에 다른 객체 예를 들어 EnglishDictionary 와 같은 객체로 변경하기가 쉽지 않다. 자연스럽게 코드는 유연하지 않을 뿐 아니라 테스트할 때에도 객체 변경이 자유롭지 못해 테스트하기 어려운 문제도 가지고 있다.

 

[ 싱글턴 구현 (아이템3) ]

 

그렇다면 아이템3에서 언급한 싱글톤으로 구현하면 괜찮을까?

 

public class SpellChecker {
    // 객체 참조
    private final Lexicon dictionary = new KoreanDictionary();

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

    public static final SpellChecker INSTANCE = new SpellChecker();

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

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

    public static void main(String[] args) {
        boolean isChecked = SpellChecker.INSTANCE.isValid("hello");
        System.out.println("isChecked = " + isChecked);
    }
}

interface Lexicon {}

class KoreanDictionary implements Lexicon {

}

class EnglishDictionary implements Lexicon {

}

 

싱글톤 구현도 다른 객체를 대체하기가 쉽지 않은 구조로 되어 있다. 책에서는 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글톤 방식이 적합하지 않다 고 언급하고 있다.


| 의존 객체 주입

 

제시하는 해결책으로는 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 의존 객체 주입 방식을 소개하고 있다. 코드를 보면 말로 설명하는 거 보면 조금 더 쉽게 이해할 수 있다.

 

public class SpellChecker {

    private final Lexicon dictionary;

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

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

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

    public static void main(String[] args) {
        SpellChecker spellChecker = new SpellChecker(new EnglishDictionary());
        boolean isChecked = spellChecker.isValid("hello");
        System.out.println("isChecked = " + isChecked);
    }
}

interface Lexicon {}

class KoreanDictionary implements Lexicon {

}

class EnglishDictionary implements Lexicon {

}

 

자원을 사용하는 쪽(해당 코드에서는 main 메서드)에서 생성하여 클래스에 넘겨주는 방식(생성자로 주입)이다. 여기서 의존 관계는 객체의 사용관계라 생각하면 된다. 이런 구조라면 SpellChecker 클래스를 수정하지 않고 여러 Dictionary 객체를 변경할 수 있게 된다.


| 변형 패턴인 팩토리 메서드 패턴

 

생성자에 자원 팩토리를 넘겨주는 방식이다. 팩토리란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어 주는 객체를 말한다. 팩토리라는 이름에서 알 수 있듯이 객체를 생산하는 공장이라 생각하면 이해가 쉽다. 자바 8에서 이러한 팩토리 메서드를 표현한 Supplier<T> 인터페이스가 있다.

 

public class SpellChecker {

    private final Lexicon dictionary;

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

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

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

    public static void main(String[] args) {
        Supplier<Lexicon> dictionary = new Supplier<Lexicon>() {
            @Override
            public Lexicon get() {
                return new KoreanDictionary();
            }
        }; // lambda 변경 가능
        SpellChecker spellChecker = new SpellChecker(dictionary);
        boolean isChecked = spellChecker.isValid("hello");
        System.out.println("isChecked = " + isChecked);
    }
}

interface Lexicon {}

class KoreanDictionary implements Lexicon {

}

class EnglishDictionary implements Lexicon {

}

 

Supplier<Lexicon> 은 메서드 레퍼런스(람다) 형태로 표현 가능하다. 대형 프로젝트에서는 스프링, 대거와 같은 의존 객체 주입 프레임워크를 활용해 보는 것도 좋은 대안이 될 수 있다.


References

  • Effective-Java 3판, 조슈아 블로크
  • https://www.youtube.com/watch?v=24scqT2_m4U, 백기선님 유튜브

'dev. > Effective-Java' 카테고리의 다른 글

(아이템 4) 인스턴스화를 막으려거든 private 생성자를 사용하라  (0) 2022.01.01
'dev./Effective-Java' 카테고리의 다른 글
  • (아이템 4) 인스턴스화를 막으려거든 private 생성자를 사용하라
minikuma
minikuma
그 길을 아는 것과 그 길을 걷는 것은 다르다!
  • minikuma
    Note
    minikuma
  • 전체
    오늘
    어제
    • 전체보기 (7)
      • dev. (7)
        • Java (2)
        • golang (3)
        • Spring (0)
        • Spring Boot (0)
        • GraphQL (0)
        • Effective-Java (2)
        • Installation (0)
        • Algorithm (0)
        • git (0)
      • error (0)
        • 에러모음집 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • hELLO· Designed By정상우.v4.10.1
minikuma
(아이템 5) 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
상단으로

티스토리툴바