// private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
구현체가 없어서 NullPointerException이 발생
OrderServiceImpl이 구현체를 정해야 하는 상황
OrderServiceImpl이 직접 객체를 생성하고 구현체의 선택도 함
관심사의 분리가 필요
별도의 설정 클래스가 구현체를 생성하고 연결
AppConfig 등장
애플리케이션의 전체 동작 방식을 구성(config)하기 위해 구현 객체를 생성하고 연결하는 책임을 가지는 별도의 클래스
AppConfig와 MemberService 관련 코드 변경
먼저 이전에 만든 MemberService와 관련된 코드부터 변경
// private final MemberRepository memberRepository = new MemoryMemberRepository();
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
MemberServiceImpl의 코드는 위와 같이 변경
주석 처리된 부분이 이전 코드로, MemoryMemberRepository라는 구현체를 선택하고 있음
변경된 코드는 구현체에 대한 정보가 존재하지 않음
DIP를 준수하는 코드가 됨
package hello.core;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
AppConfig에서 MemberServiceImpl에 대한 구현체를 선택
생성자 주입 기법
AppConfig와 OrderServiceImpl 관련 코드 변경
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
OrderServiceImpl은 구현체에 대한 정보가 두 개 필요함
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
따라서 생성자에 MemberRepository와 DiscountPolicy 두 개의 매개변수를 받음
OrderServiceImpl에는 구현체에 대한 정보가 없음
생성자에 의해 구현체가 할당됨
철저하게 DIP를 준수하는 코드(인터페이스에만 의존, 구체적인 클래스에 대해서 모름)
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
appConfig 객체가 memoryMemnerRepository(참조값: x001) 객체를 생성
그 참조값을 memberServiceImpl을 생성하면서 생성자로 전달
클라이언트인 memberServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같음
이러한 이유로 DI(Dependency Injection), 의존관계 주입 또는 의존성 주입이라고 함
변경된 OrderServiceImpl 코드
package hello.core.order;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
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;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
// OrderService 입장에서는 할인 정책에 대해서 모르고, discountPolicy에 위임함 -> 단일 책임 원칙 준수
// 할인이 변경되어도 OrderService는 바뀌지 않음
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
import에 구현체에 대한 import가 모두 사라짐
설계 변경으로 OrderServiceImpl은 FixDiscountPolicy를 의존하지 않음
단지 DiscountPolicy 인터페이스에만 의존
OrderServiceImpl 입장에서 OrderServiceImpl의 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없음
생성자를 통해서 어떤 구현 객체을 주입할지는 오직 외부(AppConfig)에서 결정
OrderServiceImpl은 이제부터 실행에만 집중
MemberApp 변경
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
// MemberService memberService = new MemberServiceImpl();
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);
System.out.println("findMember = " + findMember);
}
}
memberService에 appConfig를 이용하여 객체 생성
인터페이스에만 의존하고 구체 클래스에 의존하지 않음
실행 결과는 동일
OrderApp 변경
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.order.Order;
import hello.core.order.OrderService;
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.toString());
}
}
appConfig를 이용하여 객체 생성
인터페이스에만 의존하고 구체 클래스에 의존하지 않음
실행 결과는 동일
MemberServiceTest 변경
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
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);
}
}
@BeforeEach 어노테이션은 각 테스트의 실행 전에 실행 됨
beforeEach 메소드를 만들어 객체 생성
OrderServiceTest
package hello.core.order;
import hello.core.AppConfig;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
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() {
// primitive type인 long으로 해도 되지만, 그러면 null이 들어갈 수 없음
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
// org.assertj.core.api를 사용해야 .을 이용한 메소드 체인을 사용 가능
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}