1. 새로운 할인 정책을 적용하고자 할 경우
현재 Fix고정 할인율 정책을 discountPolicy의 구현체로 사용하고 있는데 기획자가 구매 금액에 따라서 할인을 하자고 제안하여 구현체를 바꿔 갈아끼고자 하는 상황이다.
현 상황만 간단히 살펴보면 구현체를 따로 만들어 그를 바꿔 사용하면 될 것 같아 보인다.
그렇기에 구현체를 만드는 작업부터 진행하도록 한다.
RateDIscountPolicy라는 구현 클래스를 만들어주고 코드가 확인 작업을 위해서 단위 테스트를 진행한다.
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.*;
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); --> static import 자바 문법 아래와 같이 사용 가능
assertThat(discount).isEqualTo(1000);
}
@Test
@DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
void VIP_X(){
//given
Member member = new Member(1L,"memberBASIC", Grade.BASIC);
//when
int discount = discountPolicy.discount(member,10000);
//then
assertThat(discount).isEqualTo(1000);
}
}
위의 테스트가 두가지인 이유는? (성공 테스트, 실패 테스트)
- 실패 테스트가 매우 중요하고 인텔리 제이는 이를 확실하게 보여주기 때문이다.
- 아래와 같이 기대값이 1000인데 실제로 0이 나와 테스트 오류가 난 이유를 보여주고 있다. (VIP가 아닌 경우 할인이 들어가지 않기 때문이다. )
바뀐 구현체의 테스트까지 통과를 했다면 이를 더 자세히 살펴보아야 한다.
3. 구현체의 변경 시 발생하는 문제들(OCP, DIP 문제 발생)
문제점 확인
- 역할과 구현을 잘 분리했나? -> O
- 다형성을 활용하고 인터페이스와 구현 객체를 분리했나? -> O
- OCP, DIP 같은 객체지향 설계 원칙을 준수 했나? -> X
우리는 그렇다면 OCP, DIP를 어떻게 위반하고 있는지 살펴보자.
<우리가 기대 했던 의존관계>
<실제 의존관계>
할인 정책의 구현체를 바꿔 사용하기에 앞서서 우리는 할인 정책의 클라이언트인 OrderServiceImpl를 살펴보아야 한다. 아래 코드를 통해 살펴보도록 하자.
<할인 정책의 변경 시 클라이언트 코드 변경>
- 클라이언트인 OrderServiceImpl 코드를 살펴보면 FixDiscountPolicy에 의존하고 있음을 볼 수 있다.
- 이때 DIP 문제가 나타나고 이를 해결하고자 OrderServiceImpl를 수정하는 순간 OCP 문제 역시 발생하게 된다.
- 구현체를 바꾸기 위해서 클라이언트의 코드를 변경할 때 OCP 문제가 발생하게 된다.
그렇다면 이를(OCP/DIP 문제) 해결할 방법이 있을까?
4. OCP, DIP 문제해결 방안
우리가 처음 생각했던 것 처럼 구체가 아닌 추상에만 의존하도록 설계해주면 된다.
<추상에만 의존한 의존관계 설정>
private DiscountPolicy discountPolicy; //구체가 아닌 추상화에만 의존하도록 한 것
- 위의 코드처럼 구체가 아닌 인터페이스에만 클라이언트가 의존할 수 있도록 코드를 작성해주어야 한다.
- 위의 코드는 추상 클래스(인터페이스)에만 의존하도록 의존 관계를 설정해준 코드이다.
<final이 빠진 이유는?>
private final DiscountPolicy discountPolicy; //구체가 아닌 추상화에만 의존하도록 한 것
final을 사용하게 되면 값을 실제로 할당해주어야 하기 때문에 빼고 작성해야 한다.
5. NPE(NullPointerException) 발생
그러나 실제 의존관계를 위의 코드처럼 변경해주면 NPE오류가 발생하게 되고, 우리는 이를 해결하기 위해서 누군가가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다.
NullPointException은 자바 프로그램 뿐만 아니라 다양한 개발언어에서 자주 발생합니다. 객체가 생성되지 않았다는 것은 프로그램을 가동시키는 VM에서 해당 하는 주소를 찾아갔을 때 아무 값도 없다는 뜻
📌참고: https://estrella-0707.tistory.com/5
6. 출처
'Back-End > Spring' 카테고리의 다른 글
[Spring][스프링 기본편] - 9. AppConfig 리팩토링 (0) | 2022.01.17 |
---|---|
[Spring][스프링 기본편] - 8. 의존관계 주입, 의존성 주입 (0) | 2022.01.15 |
[Spring][스프링 기본편] - 6. 주문과 할인 도메인 개발과 테스트 (0) | 2022.01.13 |
[Spring][스프링 기본편] - 5. 주문과 할인 도메인 설계 (0) | 2022.01.13 |
[Spring][스프링 기본편] - 4. 테스트(Feat. 순수 자바코드 테스트와 Junit을 사용한 테스트) (0) | 2022.01.13 |