본문 바로가기
Spring/Spring core basic

[Spring core basic] 09 - 새로운 할인 정책 개발 및 적용

by Kloong 2022. 5. 2.

참고

더보기

Spring core basic 시리즈는 김영한 님의 "스프링 핵심 원리 - 기본편" 강의를 정리한 글입니다. 글에 첨부된 사진은 해당 강의의 강의 자료에서 캡쳐한 것입니다. 제 Github에만 올려뒀다가, 정보 공유와 강의 홍보(?)를 위해 블로그에도 업로드합니다. 마크다운을 잘 쓰지 못해서 가독성이 조금 떨어지는 점 양해 바랍니다.

 

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

새로운 할인 정책 개발 및 적용

고정 금액 할인에서, 비율 기반 할인으로 정책을 바꿔보자.

RateDiscountPolicy 추가

개발

RateDiscountPolicy.java

package com.kloong.corebasic1.discount;

import com.kloong.corebasic1.member.Grade;
import com.kloong.corebasic1.member.Member;

public class RateDiscountPolicy implements DiscountPolicy{

    private int discountRate = 10;

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

        return 0;
    }
}

테스트

RateDiscountPolicyTest.java

package com.kloong.corebasic1.discount;

import com.kloong.corebasic1.member.Grade;
import com.kloong.corebasic1.member.Member;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

class RateDiscountPolicyTest {

    DiscountPolicy discountPolicy = new RateDiscountPolicy();

    @Test
    @DisplayName("VIP는 10% 할인이 적용되어야 한다")
    void vip_discount() {
        //given
        Member member = new Member(1L, "meberVIP", Grade.VIP);

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

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

    @Test
    @DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다")
    void basic_discount()
    {
        //given
        Member member = new Member(2L, "meberBASIC", Grade.BASIC);

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

        //then
        assertThat(discount).isEqualTo(0);
    }

}

적용

새롭게 개발한 할인 정책을 애플리케이션에 적용해보자.

할인 정책을 변경하려면 클라이언트인 OrderServiceImpl 코드를 고쳐야한다.

OrderServiceImpl.java

public class OrderServiceImpl implements OrderService{

    //private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();

    //생략...
}

문제점 발견!

  1. 우리는 역할과 구현을 충실하게 분리했다 -> OK
  2. 다형성도 활용하고, 인터페이스와 구현 객체를 분리했다 -> OK
  3. OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수했다 -> OK...? 그렇게 보이지만 사실은 아니다.

클래스 다이어그램으로 현재 코드에서의 의존 관계를 분석해보자.

기대했던 의존 관계

DiscountPolicy만 의존하는 것을 의도하며 개발했다.

실제 의존관계

하지만 코드를 보면 실제로는 OrderServiceImple이 DiscountPolicy 뿐만 아니라 FixDiscountPolicy/RateDiscountPolicy 라는 구현 클래스를 함께 의존하고 있다! -> DIP 위반

의존 관계에 의해 할인 정책 변경 시 발생하는 상황

DIP를 위반했기 때문에 (구현체를 의존하고 있기 때문에) FixDiscountPolicy를 RateDisconutPolicy로 변경하는 순간 OrderServiceImpl의 소스코드를 변경해야 한다! -> OCP 위반

DIP 관점

주문서비스 클라이언트인 OrderServiceImpl은 DiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것 같은데...? NO! 아니다!

클래스 의존관계를 분석해 보면, 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고 있음을 발견할 수 있다.

  • 추상(인터페이스) 의존: DiscountPolicy
  • 구체(구현) 클래스: FixDiscountPolicy , RateDiscountPolicy

OCP 관점

소프트웨어를 변경하지 않고 확장할 수 있다고 했는데, 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다! 따라서 OCP를 위반한다.

DIP, OCP 위반 해결 방법

DiscountPolicy 인터페이스 뿐만 아니라 구현 클래스에도 의존하고 있기 때문에, 즉 DIP를 위반하기 때문에, 할인 정책 변경 시 OCP를 위반하게 된다.
-> 따라서 DIP를 위반하지 않도록 추상(인터페이스)에만 의존하도록 변경하면 된다!

OrderServiceImpl.java

public class OrderServiceImpl implements OrderService{

    private DiscountPolicy discountPolicy; //NullPointerException 발생

    //생략...
}

DIP를 위반하지 않기 위해 위처럼 코드를 바꾸면 당연하게도 제대로 동작하지 않는다.

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

댓글