[Spring] 객체 지향 원리 적용

앞서 만들었던 예제 1에 새로운 할인 정책을 개발하고 적용시켜 보자


새로운 할인 정책을 확장해보자.
한 기획자가 서비스 오픈 직전에 할인 정책을 지금처럼 고정 할인이 아니라 좀 더 합리적인 주문 금액당 할인하는 정률% 할인으로 변경하고 싶다고 요청했다.

이전 개발이 정말 객체 지향 설계 원칙을 잘 준수했는지 확인해보자. 이번에는 주문한 금액의 일정%를 할인해주는 정률 할인 정책을 추가할 것이다. 

새로운 할인 정책 개발

RateDiscountPolicy 추가

discount 패키지에 RateDiscountPolicy 클래스를 추가한다.

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

 

테스트 작성

위 코드를 간단히 테스트해보자. 원래는 test폴더에 패키지를 만들고 아래에 클래스를 만들고... 와 같은 이전과 같은 과정이 필요했다. 하지만, IntelliJ에서 지원하는 기능을 사용해보자. 

discount method에 커서를 올리고 Command + Shift + T (윈도우는 Ctrl + Shift + T)를 누르고 "Create New Test..."를 선택하면  다음과 같은 화면을 볼 수 있다.

Testing library는 JUnit5로 선택해주고, Class name은 테스트하고자 하는 클래스명 뒤에 Test를 붙인 이름이 기본으로 설정되어 있다. 나머지 옵션은 그대로 두고 OK를 눌러주면 Test클래스가 생성된다. 생성된 클래스를 다음의 코드를 통해 정말 10%가 할인이 되는지 확인해보자. 

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_ok(){
        //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_no(){
        //given
        Member member = new Member(2L, "memberBASIC", Grade.BASIC);

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

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

 

테스트를 실행해보면 통과하는 것을 확인할 수 있다. 


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

이 할인 정책을 실제로 적용하려면 order 패키지의 OrderServiceImpl로 들어가야 한다. 원래는 이 클래스의 할인 정책이 

private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

이 코드로 고정 할인 정책으로 들어가 있었다. 이것을

private final DiscountPolicy discountPolicy = new RateDiscountPolicy();

이 코드로 변경해주면 수정이 완료된다. 

 

위에서 보이는 것과 같이 할인 정책을 변경하려면 클라이언트인 'OrderServiceImpl'클래스의 코드를 변경해야 한다.

역할과 구현을 충실하게 분리했고 이에 따라  다형성도 활용하고 인터페이스와 구현 객체를 분리했다. 그렇다면, OCP, DIP 같은 객체지향 설계 원칙을 준수했을까?

주문 서비스 클라이언트(OrderServiceImpl)은 'DiscountPolicy' 인터페이스에 의존하면서 DIP를 잘 지키고 있는 것 아닌가?

클래스 의존관계를 분석해보면, 인터페이스(추상) 뿐만 아니라 구현 클래스(구체)에도 의존하고 있기 때문에 DIP를 위반한 것이다. 그림을 통해 쉽게 이해해보자. 

기대했던 의존관계

우리가 기대했던 의존 관계는 위의 그림과 같고, 실제로 코드에 나온 것처럼 OrderServiceImpl은 DiscountPolicy 인터페이스에 의존하고 있다. 하지만 실제 의존관계를 살펴보면 다음과 같다. 

실제 의존관계

private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

이 코드에서와 같이 추상에도 의존(왼쪽)하고 구현(구체)에도 의존(오른쪽)하는 것을 확인할 수 있다. 이는 DIP를 위반하는 것이다. 

정책 변경

만약 여기서 정책을 변경한다면 OrderServiceImpl의 코드가 반드시 변경이 되어야 한다. 즉, 이러한 의존관계가 존재한다면 클라이언트 코드가 반드시 변경(영향)되는 것이다. 이는 OCP를 위반한 것이다. 

문제점 해결

DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경시켜 보자. 

인터페이스에만 의존하도록 코드를 변경하면, 단순하게 구현 클래스 부분을 지워주면 된다. 

private DiscountPolicy discountPolicy;

인터페이스에만 의존하도록 코드를 변경했다. 하지만 구현체가 없는데 어떻게 코드를 실행할 수 있을까? 실제로 실행해보면 NullPointerException이 발생한다. 실제 구현체가 없기 때문이다.

이 문제를 어떻게 해결해야 할까? 이 문제를 해결하기 위해서는

누군가가 클라이언트인 'OrderServiceImpl' 클래스에 'DiscountPolicy'의 구현 객체를 대신 생성하고 주입해야 한다. 

어떻게 위의 방법을 수행할 수 있는지 다음 포스팅부터 하나씩 알아보도록 하자.

'스프링(Spring) > Spring' 카테고리의 다른 글

[Spring] AppConfig 리팩토링  (0) 2022.07.29
[Spring] 관심사 분리  (0) 2022.07.13
[Spring] 예제 1 만들기_4(JAVA)  (0) 2022.07.09
[Spring] 예제 1 만들기_3(JAVA)  (0) 2022.07.07
[Spring] 예제 1 만들기_2(JAVA)  (0) 2022.07.06