2.4 새로운 할인 정책 개발
control + shift + T
테스트 클래스 자동 생성class RateDiscountPolicyTest {
RateDiscountPolicy discountPolicy = new RateDiscountPolicy();
@Test
@DisplayName("VIP는 10% 할인이 적용되어야 함")
void vip_o() {
//given
Member member = new Member(1L, "memberVIP", Grade.VIP);
//when
int discount = discountPolicy.discount(member, 10000);
//then
Assertions.assertThat(discount).isEqualTo(1000); // VIP의 할인금액 1000원
}
@Test
@DisplayName("VIP가 아니라면 할인이 적용되면 안됨")
void vip_x() {
//given
Member member = new Member(1L, "memberBASIC", Grade.BASIC);
//when
int discount = discountPolicy.discount(member, 10000);
//then
Assertions.assertThat(discount).isEqualTo(1000); // VIP의 할인금액 1000원
}
}
Expected :1000 // 기대값
Actual :0 // 실제값
import static org.assertj.core.api.Assertions.*;
@Test
@DisplayName("VIP는 10% 할인이 적용되어야 함")
void vip_o() {
//given
Member member = new Member(1L, "memberVIP", Grade.VIP);
//when
int discount = discountPolicy.discount(member, 10000);
//then
assertThat(discount).isEqualTo(1000); // VIP의 할인금액 1000원
}
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
@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);
}
}
기존 할인정책 (new FixDiscountPolicy();
구현체) 를
-> 신규 할인정책 (new RateDiscountPolicy();
구현체)로 교체
할인 정책을 변경하려면 클라이언트인 OrderServiceImpl 코드를 고쳐야 한다.
문제점 발견
우리는 역할과 구현을 충실하게 분리했다. OK
다형성도 활용하고, 인터페이스와 구현 객체를 분리했다. OK
OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수했다? 그렇게 보이지만 사실은 아니다.
DIP: 주문서비스 클라이언트( OrderServiceImpl )는 DiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것 같은데? -> 클래스 의존관계를 분석해 보자. 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고 있다. 추상(인터페이스) 의존: DiscountPolicy 구체(구현) 클래스: FixDiscountPolicy , RateDiscountPolicy OCP: 변경하지 않고 확장할 수 있다고 했는데! -> 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다! 따라서 OCP를 위반한다.
클라이언트인 OrderServiceImpl은 추상에만 의존해야하지만, 구체(구현)클래스에도 의존하고있다.
-> DIP(의존관계 역전, 인터페이스에 의존할것)를 위반하고있다.
인터페이스에만 의존하도록 설계를 변경하자.
private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); //구현체
-> 변경
private final DiscountPolicy discountPolicy; // 추상화
해결방안
즉, OrderServiceImpl이 구현 객체를 생성하고, 연결하는 책임을 지게 하면 안된다.
AppConfig
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
MemberServiceImpl
public class MemberServiceImpl implements MemberService {
// 레파지토리 구현체 생성
private final MemberRepository memberRepository;
//생성자로 의존성 주입
// MemberRepository 객체를 인자로 받아, 삽입
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
생성자 주입
다음과 같이 구현하면 MemberServiceImpl는 구현객체와 무관하게 코드를 작성할 수 있다. -> DIP를 지킬 수 있다.
appConfig
테스트 코드 변경
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
}
//테스트를 실행하기 전, 무조건 실행됨 (테스트 케이스마다 각각)
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
현재 AppConfig를 보면 중복이 있고, 역할에 따른 구현이 잘 안보인다. 중복을 제거하고, 역할에 따른 구현이 보이도록 리펙터링
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
----->
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
private MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
new MemoryMemberRepository() 이 부분이 중복 제거되었다. 이제 MemoryMemberRepository 를 다른 구현체로 변경할 때 한 부분만 변경하면 된다.
AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분 리되었다
appConfig
public class AppConfig {
...
private DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy(); //기존 고정할인
return new RateDiscountPolicy(); // 신규 정률할인
}
}
한 줄만 바꾸면 된다.
출력
order = Order{memberId=1, itemName='itemA', itemPrice=20000, discountPrice=2000}
order.calculatePrice = 18000
정률 할인 정책이 성공적으로 적용된 모습.