티스토리 뷰

728x90

서문



이펙티브 자바(Effective Java 3/E) ITEM 04 번을 기재합니다. 독학으로 공부하고 있어서 이해하지 못한 점을 찾아서 보완하고 있습니다. 틀린 점이 있다면 알려주시면 감사하겠습니다.









제 1장 객체 생성과 파괴


이 장에서는 아래와 같은 큰 3가지 관점을 제시한다.


  • 객체를 만들어야 할 때와 만들지 말아야 할 때를 구분
  • 올바른 객체 생성 방법과 불필요한 생성을 피하는 방법
  • 제때 파괴됨을 보장하고 파괴 전에 수행해야 할 정리 작업



ITEM 05 - 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라


많은 클래스가 하나 이상의 자원에 의존한다. 가령 맞춤법 검사기는 사전(Dictionary)에 의존하고, 이런 클래스를 정적 유틸리티 클래스(ITEM04)와 싱글톤(ITEM03)으로 구현한 모습을 드물지 않게 볼 수 있다.



정적 유틸리티와 싱글톤

5.1 정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어려움

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

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

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

-----------------------------------------------------------------

5.1.1 정적 유틸리티 메서드를 사용한 예시

public class SpellChecker {

    private static final Lexicon dictionary = new KoreanDictionary(); // KoreanDictionary만을 사용함

    private SpellChecker() {}


    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) {
        SpellChecker.isValid("hello");
    }

}

interface Lexicon{}

class KoreanDictionary implements Lexicon{}

-----------------------------------------------------------------

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

public class SellChecker{
    private final Lexicon dictionary = ...;
    public static SpellChecker INSTANCE = new SpellChecker(...);

    private SpellChecker(...){}

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

------------------------------------------------------------------

5.2.1 싱글톤을 사용한 예시

public class SpellChecker {

    private final Lexicon dictionary = new KoreanDictionary();

    int num;
    int count;
    int ban;

    public SpellChecker num(int num) {
        setNum(num);
        return this;
    }

    public SpellChecker count(int count) {
        setCount(count);
        return this;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public void setCount(int count) {
        this.count = count;
    }

    private SpellChecker() {}

    public static final SpellChecker INSTANCE = new SpellChecker(){};

    public boolean isValid(String word) {
        throw new UnsupportedOperationException();
    }

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

    public static void main(String[] args) {
        SpellChecker.INSTANCE.isValid("hello");
        SpellChecker.INSTANCE.num(10);
    }
}

interface Lexicon {}

class KoreanDictionary implements Lexicon{}

  • 두 방식 모두 사전을 단 하나만 사용한다고 가정한다는 점
  • 만약에 여러 사전을 사용해야 한다면 매우 유연하지 못한 경우
  • 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글톤 방식은 적합하지 않음


의존 객체 주입

  • 클래스(SpellChecker)가 여러 자원 인스턴스를 지원해야 하며, 클라이언트가 원하는 자원을 사용해야 함
  • 또한 스레드 상황에서도 생성자가 만들어 질 때 고정되기 때문에 불변을 보장
  • 이 때 사용하는 패턴이 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식(DI, 의존성 주입)
5.3 의존 객체 주입은 유연성과 테스트 용이성을 높여줌

public class SpellChecker{
    private final Lexicon dictionary;

    public SpellChecker(Lexicon dictionary){ // 생성자를 만들 때 dictionary를 주입받아서 사용
        this.dictionary = dictionary;
    }

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

-----------------------------------------------

5.3.1 의존 객체 주입을 활용한 예시

public class SpellChecker {

    private final Lexicon dictionary;

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

    public boolean isValid(String word) {
        throw new UnsupportedOperationException();
    }

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

    public static void main(String[] args) {
        SpellChecker koreanSpellChecker = new SpellChecker(new KoreanDictionary());
        SpellChecker EnglishDictionary = new SpellChecker(new EnglishDictionary());
        koreanSpellChecker.isValid("hello");
        EnglishDictionary.isValid("hello");

    }

}

interface Lexicon {}

class KoreanDictionary implements Lexicon{}

class EnglishDictionary implements Lexicon{}

  • 이 방법을 사용하면, 자원이 몇 개든 의존 관계가 어떻든 상관없이 잘 작동
  • 불변(ITEM17)을 보장하여(같은 자원을 사용하려는) 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있음
  • 의존 객체 주입은 생성자, 정적팩토리(ITEM01), 빌더(ITEM02) 모두 똑같이 응용 가능
    • 여기서 파생한 방법이 있는데 생성자에 자원 팩토리를 넘겨주는 방식(펙토리 메서드 패턴)
      • JAVA 8에서 소개한 Supplier 인터페이스가 팩토리를 표현한 완벽한 예
// Tile의 하위 클래스가 들어올 수 있다. 즉 Tile의 하위클래스를 받아 거기에 맞는 생성을 해줄 수 있음
Mosaic create(Supplier<? extends Tile> tileFactory){...} 

  • 의존 객체 주입이 유연성과 테스트 용이성을 개선해줌
  • but, 의존성이 많은 큰 프로젝트에서는 코드를 어지럽게 만들기도 함
  • Spring, Dagger, Guice와 같은 프레임워크를 사용하면 이를 해결할 수 있음


핵심


클래스가 내부적으로 하나 이상의 자원에 의존하고, 미래에 그럴 가능성이 있다면 싱글톤이나 정적 유틸리티 클래스는 부적합하다. 대신 필요한 자원을(혹은 그 자원을 만들어주는 팩토리를) 생성자(혹은 정적 팩토리빌더)에 넘겨주자.

의존 객체 주입이라는 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선해준다.




댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함