요구사항 변경
기획자가 오픈을 앞두고 프로그램의 변경을 요구한 상황이다.
서비스 오픈 직전에 할인 정책을 지금처럼 고정 금액 할인이 아니라 좀 더 합리적인 주문 금액당 할인하는 정률% 할인으로 변경하고 싶어요. 예를 들어서 기존 정책은 VIP가 10000원을 주문하든
20000원을 주문하든 항상 1000원을 할인했는데, 이번에 새로 나온 정책은 10%로 지정해두면 고객이 10000원 주문시 1000원을 할인해주고, 20000원 주문시에 2000원을 할인해주는 거에요!
고정 금액 할인정책을 변경하고 싶다고 한다.
개발자는 이에 대비하여 "유연한 설계가 가능하도록 객체지향 설계 원칙을 준수했다"
할인을 해준다는 역할에는 변화가 없으니, RateDiscountPolicy 구현체를 제작해서, 이로 변경만 해주면 끝이다.
변경사항 반영 후
구현체 개발
RateDiscountPolicy
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
public class RateDiscountPolicy implements DisountPolicy{
private int dicountPercent = 10;
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return price * dicountPercent / 100;
} else {
return 0;
}
}
}
cmd + shift + T를 누르면 자동으로 테스트를 생성할 수 있다.(Junit5권장)
- VIP인 경우 - 10%의 할인 적용
- VIP 아닌 경우 - 할인 적용 x
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.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
Assertions.assertThat(discount).isEqualTo(1000);
}
@Test
@DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
void vip_x(){
//given
Member member = new Member(2L, "memberBASIC", Grade.BASIC);
//when
int discount = discountPolicy.discount(member, 10000);
//then
Assertions.assertThat(discount).isEqualTo(1000); // 원래 결과는 0 이어야함
}
}
테스트가 실패한 경우, 기대한 값과 실제로 나온 값을 다음과 같이 보여준다.
1000을 0으로 변경해주면 테스트가 통과된다.
새로운 할인 정책(구현체) "적용"
새로만든 RateDiscountPolicy를 적용하려면 단순하게, DiscountPolicy를 의존하던(클라이언트인) OrderServiceImpl에서
Fix구현체를 Rate구현체로 변경해주기만 하면 완료된다.
OrderServiceImpl
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
// private final DisountPolicy disountPolicy = new FixDiscountPolicy();
private final DisountPolicy disountPolicy = new RateDiscountPolicy();
}
여기서 문제점이 발견된다
- 역할과 구현을 분리했는가? OK
- 다형성을 활용하고, 인터페이스와 객체를 분리했는가? OK
- OCP, DIP와 같은 객체지향 원칙을 준수했는가? NO
OCP, DIP를 준수했는가?
그렇게 보이지만 사실은 아니다.
DIP 위반
주문 서비스 클라이언트(OrderServiceImpl)는 DiscountPolicy뿐 아니라 FixDiscountPolicy도 의존하고있다.
추상(인터페이스)뿐 아니라 구체(구현)클래스에도 의존하고 있다.
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DisountPolicy disountPolicy = new FixDiscountPolicy();
..
}
OCP위반
클라이언트의 변경없이 확장이 가능하다고 했는데,
위 코드에서 RateDiscountPolicy로 변경하기 위하여, 클라이언트인 OrderServiceImpl을 변경해야만 했다.
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
// private final DisountPolicy disountPolicy = new FixDiscountPolicy();
private final DisountPolicy disountPolicy = new RateDiscountPolicy();
기대했던 의존관계
실제 의존관계
이렇게 두가지를 의존하고 있기 때문에, RateDiscountPolicy로 변경하기 위해서는 OrderSerivceImpl코드를 변경해야한다.
해결책
DIP / OCP원칙을 지키도록 코드를 변경하자
추상(인터페이스)에만 의존하도록 의존관계를 변경하면된다.
구현체에는 의존하지 않도록 코드를 변경하면, 다음과 같다.
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
private DisountPolicy disountPolicy; //인터페이스 (추상)에만 의존함
// 추상뿐 아니라 구현클래스에도 의존중
// private final DisountPolicy disountPolicy = new FixDiscountPolicy();
// private final DisountPolicy disountPolicy = new RateDiscountPolicy();
}
*final을 붙이면, 값을 무조건 할당해야한다
이렇게 되면, nullpointerException이 발생한다.
이를 해결하기 위해서는
누군가가 클라이언트인 OrderServiceImpl 에 DiscountPolicy(인터페이스) 의 구현 객체를 대신 생성하고 주입해주어야 한다.
'Backend > Spring' 카테고리의 다른 글
[Spring] 순수 자바의 문제와 해결 3) 요구사항 변경 반영(OCP) (0) | 2021.09.24 |
---|---|
[Spring] 순수자바의 문제와 해결 2) 관심사의 분리(SRP/DIP) (0) | 2021.09.23 |
[Spring] 순수 자바 예제 3) - 주문/할인 도메인 개발&테스트 (0) | 2021.09.22 |
[Spring] 순수 자바 예제 2) - 회원 도메인 개발&테스트 (0) | 2021.09.22 |
[Spring] 순수 자바 예제 1) 회원 & 주문/할인 도메인 설계 (0) | 2021.09.20 |