이전 포스팅에서 DIP와 OCP를 해결하기 위하여, 클라이언트 코드에서 구체화 코드를 지워 추상화에만 의존하도록 변경하였다.
하지만, 동작시키면 구현체가 적용되어있지 않아서 NullPointException Error가 발생한다. 이를 해결하기 위한 방안을 이번 포스팅에서 다룰 것이다.
애플리케이션을 하나의 공연이라 생각해보자. 각각의 인터페이스를 배역(배우 역할)이라 생각하자. 배역에 맞는 배우를 선택하는 것은 누가 하는가?
이전 코드는 A 배역(인터페이스)을 하는 S 배우(구현체)가 B 배역(인터페이스)을 하는 T 배우(구현체)를 직접 초빙해야 하는 다양한 책임을 갖고 있다.
배우는 배역에만 집중해야 한다.
공연을 구성하고, 담당 배우를 섭외하고, 역할에 맞는 배우를 지정하는 책임을 담당하는 별도의 공연 기획자가 나올 시점이다.
package hello2.core2;
import hello2.core2.discount.FixDiscountPolicy;
import hello2.core2.member.MemberRepository;
import hello2.core2.member.MemberService;
import hello2.core2.member.MemberServiceImpl;
import hello2.core2.member.MemoryMemberRepository;
import hello2.core2.order.OrderService;
import hello2.core2.order.OrderServiceImpl;
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(),
new FixDiscountPolicy());
}
}
참고 : 지금은 각 클래스에 생성자가 없어서 컴파일 오류가 발생한다. 바로 다음에 코드에서 생성자를 만든다.
package hello2.core2.member;
public class MemberServiceImpl implements MemberService {
private final 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);
}
}
MemberServiceImpl은 MemoryMemberRepository를 의존하지 않는다.
단지 MemberRepostory 인터페이스에만 의존한다.
MemberServiceImpl은 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.
객체의 생성과 연결은 Appconfig가 담당한다.
DIP 완성 : MemberServiceImpl은 MemberRepository인 추상에만 의존하면 된다.
관심사의 분리 : 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리되었다.
package hello2.core2.order;
import hello2.core2.discount.DiscountPolicy;
import hello2.core2.discount.FixDiscountPolicy;
import hello2.core2.discount.RateDiscountPolicy;
import hello2.core2.member.Member;
import hello2.core2.member.MemberRepository;
import hello2.core2.member.MemoryMemberRepository;
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository ;
private final DiscountPolicy discountPolicy;
//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
package hello2.core2;
import hello2.core2.member.Grade;
import hello2.core2.member.Member;
import hello2.core2.member.MemberService;
import hello2.core2.member.MemberServiceImpl;
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
Member member = new Member(1l, "memberA", Grade.Vip);
memberService.join(member);
Member findMember = memberService.findMember(1l);
System.out.println("new member = " + member.getName());
System.out.println("findMember = " + findMember.getName());
}
}
package hello2.core2;
import hello2.core2.member.Grade;
import hello2.core2.member.Member;
import hello2.core2.member.MemberService;
import hello2.core2.member.MemberServiceImpl;
import hello2.core2.order.Order;
import hello2.core2.order.OrderService;
import hello2.core2.order.OrderServiceImpl;
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
Long memberId = 1l;
Member member = new Member(memberId, "memberA", Grade.Vip);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
System.out.println("order = " + order);
}
}
package hello2.core2.member;
import hello2.core2.AppConfig;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class memberServiceTest {
MemberService memberService;
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
@Test
void join() {
//given
Member member = new Member(1L, "memberA", Grade.Vip);
//when
memberService.join(member);
Member findMember = memberService.findMember(1l);
//then
Assertions.assertThat(member).isEqualTo(findMember);
}
}
package hello2.core2.order;
import hello2.core2.AppConfig;
import hello2.core2.member.Grade;
import hello2.core2.member.Member;
import hello2.core2.member.MemberService;
import hello2.core2.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class OrderServiceTest {
MemberService memberService;
OrderService orderService;
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.orderService();
}
@Test
void createOrder() {
Long memberId = 1l;
Member member = new Member(memberId, "memberA", Grade.Vip);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
테스트 코드에서 @BeforeEach는 각 테스트를 실행하기 전에 호출된다.