본문 바로가기

웹 개발/Spring

[Spring 기본] 새로운 할인 정책 개발

새로운 할인 정책 개발


요구 사항이 변경되어 새로운 할인 정책을 추가해야 된다.

할인 정책이 기존에는 고정 금액 할인이었으나, 주문 금액 당 할인하는 정률 %할인으로 변경한다. 객체 지향 설계의 원칙을 준수했다면 쉽게 할인 정책을 변경할 수 있을 것이다. 코드를 통해 확인해보자. 

 

RateDiscountPolicy 추가


 

RateDiscountPolicy.java

package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;

public class RateDiscountPolicy implements DiscountPolicy {

    private int discountPercent= 10;

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return price * discountPercent /100;
        } else {
            return 0;
        }

    }
}

 

테스트코드를 작성해보자. 

shift+command+t

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() {
        Member member = new Member(1L, "memberVip", Grade.BASIC );

        //when
        int discount = discountPolicy.discount(member, 10000);

        //then
        Assertions.assertThat(discount).isEqualTo(1000);
    }
}

=> 테스트는 실패하는 경우도 확인해야 한다. 등급이 basic일 때 실패하는지도 확인하자. 

 

 

새로운 할인 정책 적용과 문제점


실제 적용하려면 클라이언트인 OderServiceImple 을 수정해야 한다. 


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);
    }
}

=> FixDicountPolicy를 RateDiscountPolicy로 변경한다. 

 

"문제점"

  • 역할과 구현을 분리했는가? OK
  • 다형성도 활용하고, 인터페이스의 구현 객체를 분리했는가? OK
  • OCP, DIP 같은 객체지향 설계원칙을 충실히 준수했는가? => 그렇게 보이지만 사실은 아니다. 
    • DIP 주문서비스 클라이언트인 OrderServiceImpl 는 DiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것 같지만 그렇지 않다.  클래스의 의존관계를 분석해보면 추상(인터페이스)뿐안 아니라 구체(구현) 클래스에도 의존하고 있다. 
    • OCP: 변경하지 않고 확장할 수 있어야 하는데 지금 코드는 기능을 확장하면서 변경하면, 클라이언트 코드에 영향을 준다. 

 

=> 클라이언트인 OrderServiceImpl이 DiscountPolicy 뿐만 아니라 FixDiscountPolicy 인 구체 클래스도 함께 의존하고 있다. DIP를 위반한 것이다. 

따라서 FixDiscountPolicy에서 RateDiscountPolicy로 변경하는 순간 OrderServiceImpl도 변경해아 하고 이는 OCP를 위반하게 된다. 

 

 

어떻게 문제를 해결할 수 있을까?


DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경하면 된다.

OrderServiceImpl.java

public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    private DiscountPolicy discountPolicy;

    @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);
    }
}

=> 인터페이스에만 의존하도록 코드를 변경했다. 그러나 할인정책이 정해지지 않았기 때문에 nullPointException이 발생한다. 

 

"해결방안"

이 문제를 해결하기 위해서는 누군가가 클라이언트인 OrderServiceImpl에 DiscountPolicy 의 구현 객체를 대신 생성하고 주입해주어야 한다. 

다음 포스팅에서 구체적인 해결 방안에 대해 알아 볼 것이다. 

 

 

 

* 인프런 '스프링 핵심 원리 -기본편' 강의를 참고하여 작성했습니다.