(인프런) 김영한님의 스프링 핵심 원리-기본편을 공부하고 리뷰한 글입니다.
1. 새로운 할인 정책 개발
1. 새로운 할인 정책을 확장해보자
2. RateDiscountPolicy 추가
주문한 금액의 %를 할인해주는 새로운 정률 할인 정책을 추가하자.
static import
3. RateDiscountPolicy 코드 추가
DiscountPolicy 를 구현한 정률 할인 정책 클래스를 만든다.
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
public class RateDiscountPolicy implements DiscountPolicy{
private int discountPercent = 10; // 할인율(10%)
@Override
public int discount(Member member, int price) {
// VIP이면 10% 할인
if(member.getGrade() == Grade.VIP) {
return price * discountPercent / 100;
} else {
return 0;
}
}
}
4. 테스트 작성
위에서 만든 RateDiscountPolicy 를 테스트 하는 코드다.
discount 메서드가 잘 작동하는지 확인하기 위해서 할인이 적용되어야 하는 경우 & 할인이 적용되지 않아야하는 경우 2가지를 테스트하는 메서드를 만들었다.
성공할 경우와 실패할 경우 2가지 모두를 테스트하는 것이 좋은 테스트 코드다.
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
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
assertThat(discount).isEqualTo(1000);
}
// 실패 테스트
@Test
@DisplayName("VIP가 아니면 10% 할인이 적용되지 않아야 한다.")
void vip_x() {
// given
Member member = new Member(2L, "memberBASIC", Grade.BASIC);
// when
int discount = discountPolicy.discount(member, 10000);
// then
assertThat(discount).isEqualTo(0);
}
}
2. 새로운 할인 정책 적용과 문제점
1. 정률 할인 정책 적용하기
할인 정책을 고정 할인 정책-> 정률 할인 정책으로 변경하려면 클라이언트인 OrderServiceImpl 클래스의 코드를 수정해야 한다.
public class OrderServiceImpl implements OrderService{
...
//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
// 정률 할인 정책으로 변경
private DiscountPolicy discountPolicy = new RateDiscountPolicy();
...
}
<문제점>
다형성을 활용하여 역할(인터페이스)과 구현(구현 객체)를 분리했지만,
1) OCP 위반
위 코드처럼 정률 할인 정책으로 변경하려면 코드를 고쳐야하므로 클라이언트(OrderServiceImpl)에 영향을 준다.
2) DIP 위반
주문 서비스 클라이언트(OrderServiceImpl)은 실제로는 인터페이스 뿐아니라 구현 클래스에도 의존하고 있다.
- 추상(인터페이스) 의존: DiscountPolicy
- 구체(구현) 클래스 의존: FixDiscountPolicy / RateDiscountPolicy
2. 클라이언트 코드를 변경해야 하는 이유
1) 기대했던 의존 관계
지금까지 OrderServiceImpl은 단순히 DiscountPolicy 인터페이스만 의존한다고 생각했다.
2) 실제 의존 관계
하지만, 실제로 OrderServiceImpl은 DiscountPolicy 인터페이스와 FixDiscountPolicy 구체 클래스도 의존 하고 있다. -> DIP 위반!
3. 정책 변경
FixDiscountPolicy 를 RateDiscountPolicy 로 변경하는 순간 OrderServiceImpl의 소스 코드도 함께 변경해야한다. -> OCP 위반!
4. 어떻게 문제를 해결할 수 있을까?
인터페이스만 의존하도록 설계를 변경하자!
5. 인터페이스에만 의존하도록 코드 변경
public class OrderServiceImpl implements OrderService {
...
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
...
}
아래와 같이 인터페이스에만 의존하도록 설계와 코드를 변경한다.
하지만, 실행해보면 NPE(NullPointException)이 발생한다. 구현체가 없기 때문이다.
<해결방안>
누군가가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다.