본문 바로가기

카테고리 없음

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

많은 객체는 하나이상의 자원을 의존한다.

아래와같이 자동 LottoTicketGenerator 객체가 있다고 가정해보자.

class LottoTicketGenerator{
  private final NumberGenerator generator = new RandomNumberGenerator();

  public LottoTicket generate(){
    return new LottoTicket(generator.generate());
  }
}
class RandomNumberGenerator implements NumberGenerator{

  public List<Integer> generate(){
        return IntStream.rangeClosed(LOTTO_NUMBER_BEGIN_BOUND, LOTTO_NUMBER_END_BOUND)
                                .boxed()
                .collect(toList());
  }
}

interface NumberGenerator{
  List<Integer> generate();
}

 

LottoTicketGenerator는 RandomNumberGenerator를 직접적으로 의존할 수 있다.

이렇게되면,

  1. 로또티켓 발급방식이 바뀌면?
  2. 테스트코드는 어떻게 작성하지?

와 같은 문제가 생길 수 있다. 즉, 하나의 넘버제너레이터로 모든 로또티켓을 발급에 대응하는 것은 사실상 불가능한 설계이다.

 

첫번째 해결책은 아래와같다.

class LottoTicketGenerator{
  private NumberGenerator generator = new RandomNumberGenerator();

  public LottoTicket generate(){
    return new LottoTicket(generator.generate());
  }

  public void changeGenerator(NumberGenerator generator){
    this.generator = generator; // Not ThreadSafe
  }
}

 

의존하던 객체에 있던 final 을 제거하고 변경가능한 메서드를 추가했다.

하지면, 이렇게 사용하면 멀티스레드환경에서 예측할 수 없는 NumberGenerator를 사용할 수 있기 때문에 사용하면 안된다.

이런 클래스의 특징은 사용하는 자원에따라 동작이 달라진다는 점이다.

 

 

진짜 해결책은 아래와같다.

class LottoTicketGenerator{
  private final NumberGenerator generator;

  public LottoTicketGenerator(final NumberGenerator generator){ // 의존객체를 주입한다
    this.generator = Objects.requireNonNull(generator);
  }

  public LottoTicket generate(){
    return new LottoTicket(generator.generate());
  }
}

 

위와같은 의존객체 주입 방식은 아래의 장점을 지닌다.

  1. 자원이 몇개든, 의존이 몇개든 문제없이 동작한다.
  2. 불변을 보장하여(변경될 일이 없기 때문에) 같은 자원을 사용하려는 여러 클라이언트가 안심하고 공유할 수 있다.

 

 

비슷한 변형으로 생성자에 자원 팩터리를 넘겨줄 수 있다.

여기서 자원 팩터리란 자원을 계속해서 찍어내는 객체를 말하는데 자바 8의 Supplier 인터페이스가 대표적이다

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get(); // T 타입 객체를 찍어낸다
}

 

아래처럼 응용할 수 있다.

public class LottoTicketGenerator {

    private final NumberGenerator numberGenerator;

    public LottoTicketGenerator(Supplier<? extends NumberGenerator> numberGenerator) {
        this.numberGenerator = numberGenerator.get();
    }
}
    @DisplayName("TestNumberGenerator를 찍어내도록")
    @Test
    void test(){
        LottoTicketGenerator generator = new LottoTicketGenerator(TestNumberGenerator::new);
    }

    class TestNumberGenerator implements NumberGenerator{

    }

 

 

일반적으로 한정적 와일드카드타입을 통해 매개변수 타입을 제한하는 것이 특징이다