본 게시글은 김영한님의 스프링 핵심 원리 기본편을 정리한 글입니다.
이전 글에서 마지막에 이제 OCP, DIP를 지키기 위하여서 구현체를 의존하지 않게 하기 위하여서
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy;
위의 주석 코드를 아래처럼 바꿔서 진행을 해야 한다고 하였다. 그러나 이렇게 진행할 경우 구현체가 없이 코드를 실행할 수가 없어 NPE가 발생하는 문제가 있다고 하였다. 그렇다면 이번 시간에는 이러한 문제를 해결할 수 있도록 하기 위하여서 어떻게 해야하는지 알아보자.
지금 우리가 생각해야될 것이 구현체에 신경을 쓰지 않아도 추상체만 보고 할인 정책을 바꿀 수 있도록 변경을 해야한다. 이것을 기억하고 이야기를 들어보자!
애플리케이션을 하나의 공연으로 생각을 할 때, 각각의 인터페이스를 배역(역할)이라고 생각을 하고, 배우를(구현)이라고 생각을 해본다.
그렇게 할 때 이 배역의 배우는 누가 하는 것일까?
현실세계에서는 이 역할을 기획자라는 사람이 나타나서 각 배역에 맞는 배우를 지정을 하게 된다. 배우가 지정하는 것이 아니란 이 말이다.
예를 들자면 디카프리오라는 배우가 로미오 역할을 하는데 줄리엣을 연기해야 하는 여자 주인공을 직접 초빙해야 하는 것과 같은 상황이 우리의 코드 상황이었다. 그래서 디카프리오는 엄청난 열정맨이기에 여러가지 책임을 도맡고 있다는 상황이 된 것이었다.
애플리케이션의 전체 동작을 기획,구성(config)하기 위한 구현 객체를 생성하고, 연결을 하는 책임을 가지는 별도의 config 클래스를 하나 만들어보자.
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService(){
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
위 코드를 보게 되면 Config 클래스에서 memberService, orderService를 부르게 되면 실질적으로 각 구현 객체를 생성을 하게 된다. MemberServiceImpl, MemoryMemberRepository, OrderServiceImpl, FixDisCountPolicy
AppConfig는 생성한 객체 인스턴스의 참조(래퍼런스)를 생성자를 통해서 주입(연결)해준다.
MemberService -> MemoryMemberReposiotry
OrderServiceImpl -> MemoryMemberRepository, FixDiscountPolicy
그렇다면 AppConfig에서 만든 내용을 바탕으로 기존에 있는 각 구현객체 코드들에도 생성자를 주입시켜주자.
public class MemberServiceImpl implements MemberService{
MemberRepository memberRepository;
//추가된 내용
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
이제 위의 코드에서 생성자를 통하여서 memberRepository를 받아오는데 구현객체가 어떤 것이 오는지는 MemberServiceImpl에서는 알 수 없다. 이제는 AppConfig를 통하여서 어떤 Repository를 사용을 할 지 결정이 되어지기 때문에 결론적으로는 이제는 구현체에 신경을 쓰지 않고 그저 추상화에만 신경을 써주면 된다. -> 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중을 하면 된다.
위와 같이 변경이 되었을때 이제 클래스 다이어그램과 인스턴스 다이어그램은 어떻게 되었는지 확인을 해보자.
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
//추가된 코드
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) { // impl 입장에서는 외부에서 의존성 주입 DI
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountP=discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountP);
}
}
OrderServiceImpl도 마찬가지이다. 이제는 더 이상 구현 객체에 신경을 쓰지 않고 실행에만 집중을 하면 되는 코드로 바뀌게 되었다.
이제 다 설정도 마무리 했으니 마지막으로 잘 되니 테스트코드를 꼭 작성해보도록 하자.
public class memberTest {
MemberRepository memberRepository;
MemberService memberService;
@BeforeEach
void beforeEach(){
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
.
.
.
public class OrderServiceTest {
MemberService memberService;
OrderService orderService;
@BeforeEach
void beforeEach(){
AppConfig appconfig = new AppConfig();
memberService = appconfig.memberService();
orderService = appconfig.orderService();
}
.
.
.
.
설정상으로는 이제 문제는 없지만 더 깔끔하게 보이도록 다음시간에는 리팩토링을 해보도록 하자!