애플리케이션을 하나의 공연이라고 생각해봅시다!
이전에서 우리 코드는...🙄
public class MemberServiceImp implements MemberService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
...
구현체가 구현체를 호출했다.
즉, 로미오 역할을 하는 디카프리오가 줄리엣 역할을 하는 배우를 직접 초빙하는 것과 같다.
즉, 로미오 역할의 디카프리오는 공연도 해야하고 배우를 초빙해야하는 다양한 책임을 가지고 있다.
공연 기획자를 만들고, 배우와 공연 기획자와 책임을 확실히 분리하자.
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImp(new MemoryMemberRepository());
}
public OrderService orderService(){
return new OrderServiceImp(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
- AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
MemberServiceImpl
MemoryMemberRepository
OrderServiceImpl
findDiscountPolicy
- AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.
MemberServiceImpl
→MemoryMemberRepository
OrderServiceImpl
→MemoryMemberRepository
,FixDiscountPolicy
public class MemberServiceImp implements MemberService{
private final MemberRepository memberRepository;
// 생성자를 통해서 MeberRepository 구현체가 무엇이 들어갈지 설정해준다.
// MemberServiceImp 코드에 MemoryMemberRepository가 없는 것을 확인할 수 있다.
// 추상화에 의존하면서 DIP를 지킬 수 있게 된다.
// 이를 생성자 주입이라고 한다.
public MemberServiceImp(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
...
}
설계 변경으로 MemberServiceImpl
은 MemoryMemberRepository
를 의존하지 않는다.
MemberRepository
인터페이스만 의존한다.MemberServiceImpl
입장에서 생성자를 통해 이전 구현 객체가 들어올지 주입될지 알 수 없다.MemberServiceImpl
의 생성자를 통해서 어떤 구현 객체를 주입할지는 외부 AppConfig
에서 결정된다.MemberServiceImpl
은 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.AppConfig
가 담당한다.MemberServiceImpl
은 MemberRepository
인 추상에만 의존하면 된다.public class AppConfig {
public MemberService memberService(){
return new MemberServiceImp(new MemoryMemberRepository());
}
public OrderService orderService(){
return new OrderServiceImp(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
AppConfig
객체는 MemoryMemberRepository
객체를 생성하고 그 참조값을 MemberServiceImpl
을 생성하면서 생성자로 전달한다.MemberServiceImp
입장에서 보면 의존관계를 마치 외부에서 주입해주는 것과 같다고 해서 DI(의존관계 주입, 의존성 주입)이라고 한다. public class OrderServiceImp implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImp(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
OrderServiceImpl
은 FixDiscountPolicy
인 구체 클래스를 의존하지 않는다.DiscountPolicy
추상 클래스에만 의존한다.OrderServiceImpl
입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지) 알 수 없다.OrderServiceImpl
의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부 (AppConfig
)에서 결정한다.OrderServiceImpl
은 이제부터 실행에 대한 책임만 가지게 된다.OrderServiceImpl
에는 MemoryMemberRepository
와 FixDiscountPolicy
객체의 의존관계가 주입된다.public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
// MemberService memberService = new MemberServiceImp();
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("find Member = " + findMember.getName());
}
}
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);
System.out.println("order.calculator = "+ order.calculatorPrice());
}
}
@BeforeEach
를 통해 각 테스트를 실행하기 전에 AppConfig
를 호출한다.
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);
}
}
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);
}
}
AppConfig
를 통해서 관심사를 확실히 분리한다.AppConfig
는 공연 기획자!AppConfig
는 구체 클래스를 선택 → 배역에 맞는 담당 배우를 선택한다.OrderServiceImpl
등과 같은 구체 클래스는 기능을 실행하는 책임만 진다 → 각 배우들은 담당 기능을 실행하는 책임만 지면 된다.AppConfig를 보면 중복이 있고, 역할에 따른 구현이 잘 안 보인다.
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImp(new MemoryMemberRepository());
}
public OrderService orderService(){
return new OrderServiceImp(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImp(memberRepository());
}
private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImp(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
}
new MemoryMemberRepository()
이 부분이 중복 제거 되었다.new MemoryMemberRepository()
를 다른 구현체로 변경할 때 한 부분만 변경하면 된다.AppConfig
를 보면 역할과 구현 클래스가 한 눈에 들어온다.