225일차(모험 134일차)- 생성자 주입을 선택해라!
[add] 생성자 주입을 선택해라! [#32] #34
프레임워크 없이 순수한 자바 코드를 단위테스트 하는 경우 아래와 같이 수정자 의존관계일 때
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
// public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
// this.memberRepository = memberRepository;
// this.discountPolicy = discountPolicy;
// }
}
이에 대한 test 코드를 작성했다.
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.createOrder(1L, "itemA", 10000);
}
* 참고 : test 실행 전에 AppConfig에서 아래와 같이 return 값 변경해줘야 한다.
@Bean
public OrderService orderService() {
System.out.println("call AppConfig.orderService");
// return new OrderServiceImpl(memberRepository(), discountPolicy());
return null;
}
실행하면 NPE(Null Point Exception)이 발생하는데
memberRepository, discountPolicy 모두 의존관계 주입이 누락되었기 때문이다.
* memberRepository, discountPolicy에 값을 세팅해줘야 한다.
* 다시 원상 복귀한다.
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
생성자 주입으로 사용하게 되면 아래와 같이 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다.
그리고 IDE에서 바로 어떤 값을 필수로 주입해야 하는지 알 수 있다.
생성자 주입으로 test해보기
아래 코드는 spring 없이 순수한 java 코드로 필요한 것들을 test 해서 조립한 것이다.
test 코드 상에서 필요한 구현체들을 조합
class OrderServiceImplTest {
@Test
void createOrder() {
MemoryMemberRepository memberRepository = new MemoryMemberRepository();
memberRepository.save(new Member(1L, "name", Grade.VIP));
OrderServiceImpl orderService = new OrderServiceImpl(memberRepository, new FixDiscountPolicy());
Order order = orderService.createOrder(1L, "itemA", 10000);
assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
final
생성자를 쓰면 필드에 final 키워드를 사용할 수 있다.
그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
생성자에서만 값을 세팅할 수 있다.
실수로 생성자를 만드는데 코드를 누락했다고 가정한다. (주석 처리된 부분)
테스트 코드(OrderServiceImplTest)를 실행시키면 테스트 코드를 작성할 때 까지는 문제가 없는데
실행을 시키면 오류가 뜬다.
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
// this.memberRepository = memberRepository;
// this.discountPolicy = discountPolicy;
}
이 부분을 막아줄 수 있는 것이 final이다.
final을 작성하면 아래와 같이 컴파일 오류가 뜬다.
생성자 주입을 사용하면 불변, 누락을 막을 수 있다.
또, final 키워드를 넣을 수 있다.
생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다.
생성자 주입이 아니면 final 키워드가 불가능 하다. set~은 객체가 생성된 다음에 호출이 되므로
수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없다.
오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.
정리
생성자 주입 방식을 선택하는 이유는 여러가지가 있지만, 프레임워크에 의존하지 않고,
순수한 자바 언어의 특징을 잘 살리는 방법이기도 하다.
기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면 된다.
생성자 주입과 수정자 주입을 동시에 사용할 수 있다.
항상 생성자 주입을 선택한다. 그리고 가끔 옵션이 필요하면 수정자 주입을 선택한다.
필드 주입은 사용하지 않는게 좋다.