많은 객체는 하나이상의 자원을 의존한다.
아래와같이 자동 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를 직접적으로 의존할 수 있다.
이렇게되면,
- 로또티켓 발급방식이 바뀌면?
- 테스트코드는 어떻게 작성하지?
와 같은 문제가 생길 수 있다. 즉, 하나의 넘버제너레이터로 모든 로또티켓을 발급에 대응하는 것은 사실상 불가능한 설계이다.
첫번째 해결책은 아래와같다.
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());
}
}
위와같은 의존객체 주입 방식은 아래의 장점을 지닌다.
- 자원이 몇개든, 의존이 몇개든 문제없이 동작한다.
- 불변을 보장하여(변경될 일이 없기 때문에) 같은 자원을 사용하려는 여러 클라이언트가 안심하고 공유할 수 있다.
비슷한 변형으로 생성자에 자원 팩터리를 넘겨줄 수 있다.
여기서 자원 팩터리란 자원을 계속해서 찍어내는 객체를 말하는데 자바 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{
}
일반적으로 한정적 와일드카드타입을 통해 매개변수 타입을 제한하는 것이 특징이다