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

DEINGVELOP·2022년 10월 12일
0

Effective Java

목록 보기
1/19

자원을 명시해서 맞춤법 검사기와 사전 구현하기

많은 클래스가 하나 이상의 자원에 의존한다. 가령, '맞춤법 검사기'는 '사전'에 의존하고 있다.


📝 구현 방법 1. 정적 유틸리티 클래스

public class SpellChecker {

  	private static final Lexion dictionary = ...;
      
    private SpellChecker() {} 	// 객체 생성 방지용
    
    public static boolean isValid() {...}
    public static List<String> suggestions(String typo) {...}

}

📝 구현 방법 2. 싱글턴

public class SpellChecker {

  	private static final Lexion dictionary = ...;
      
    private SpellChecker(...) {}
    public static SpellChecker INSTANCE = new SpellChecker(...);
    
    public static boolean isValid() {...}
    public static List<String> suggestions(String typo) {...}

}

👉🏻 평가

위 두 방식은 모두 '사전을 단 하나만 사용한다'고 가정한다는 점에서 아쉬움이 있다. 사전 하나로 모든 쓰임에 대응하기에는 힘들 수 있기 때문!

실전에서는 사전이 언어별로 따로 있고, 특수 어휘용 사전을 별도로 두기도 하기 때문이다. 심지어는 테스트용 사전도 필요할 수 있다.


💡 그렇다면?

시도 가능한 방법
: dictionary 필드에서 final 한정자를 제거하고 다른 사전으로 교체하는 메서드를 추가하기

  • 어색한 방식임
  • 오류를 내기 쉬움
  • 멀티스레드 환경에서는 쓸 수 없음

👉🏻 결론

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



Dependenct Injection

: 의존관계 주입, 의존성 주입

"A가 B를 의존한다"는 의미는 무엇일까? 의존 관계에 대해 토비의 스프링에서는 다음과 같이 정의하고 있다.

의존 대상 B가 변하면, 그것이 A에 영향을 미친다.
                  - 이일민, 토비의 스프링 3.1 中 -


의존 객체 주입

  • 의존성 주입의 한 형태

  • 클래스(ex: SpellChecker)가 여러 자원 인스턴스를 지원할 수 있으며, 클라이언트가 원하는 자원을 사용해야 함

  • 많은 프로그래머가 이 방식에 이름이 있다는 사실도 모른 채 사용해왔음


1. 맞춤법 검사기와 사전 구현해보기

: 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주기(주입하기)

public class SpellChecker {
	
    private final Lexion dictionary;
    
    public SpellChecker(Lexion dictionary) {
    	this.dictionary = Objects.requireNonNull(dictionary);
    }
    
    public boolean isValid(String word) {...}
    public List<String> suggestions(String typo) {...}
}
  • 맞춤법 검사기를 생성할 경우, 의존 객체인 사전을 주입해주는 방식

👉🏻 효과

  • 유연성테스트 용이성을 높여줌
  • 위에서는 dictionary라는 딱 하나의 자원만 사용하지만, 자원의 개수와 의존관계 형태에 상관 없이 잘 작동함
  • 불변을 보장(private, final 등 이용)하여 같은 자원을 사용하려는 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있음
  • 의존 객체 주입은 생성자 뿐만 아니라 정적 팩터리, 빌더 모두에 똑같이 응용할 수 있음

2. 이 패턴의 변형

: 생성자에 자원 팩터리를 넘겨주는 방식

  • Factory : 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체

  • 즉, Factory Method Pattern을 구현하는 것

  • ex: Supplier<T> 인터페이스가 팩터리를 표현한 완벽한 예시

    • Supplier<T>를 입력 받는 메서드는 일반적으로 한정적인 와일드카드타입을 사용해 팩터리의 타입 매개변수를 제한해야 함
  • 이 방식을 사용해 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있음

  • 클라이언트가 제공한 팩터리가 생성한 타일(Tile)들로 구성된 모자이크(Mosaic)를 만드는 메서드

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

3. 유의할 점

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

👉🏻 해결책

  • Dagger(대거)
  • Guice(주스)
  • Spring(스프링)

등의 의존 객체 주입 프레임워크를 사용하면 이러한 어질러짐을 해소할 수 있음

* 의존 객체를 주입하도록 설계된 API를 알맞게 응용해 사용하고 있음



✍🏻 정리

클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면!

⛔ 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.

⛔ 이 자원들을 클래스가 직접 만들게 해서도 안 된다.

✅ 필요한 자원 (혹은 그 자원을 만들어주는 factory)을 생성자(혹은 정적 팩터리, 빌더)에 넘겨주자.

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





참고자료

0개의 댓글