[스프링 핵심 원리 - 기본편] 스프링 핵심 원리 이해2 - 객체 지향 원리 적용 (새로운 할인 정책)

Hyeonjun·2022년 8월 21일
0
post-thumbnail

새로운 할인 정책 개발

새로운 할인 정책을 확장해보자

  • 악덕기획자
    • 서비스 오픈 직전에 할인 정책을 지금처럼 고정 금액 할인이 아니라 좀 더 합리적인 주문 금액당 할인하는 정률% 할인으로 변경하고 싶어요.
    • 예를 들어서 기존 정책은 VIP가 10000원을 구문하든 20000원을 주문하든 항상 1000워을 할인했는데, 이번에 새로 나온 정책은 10%로 지정해두면 고객이 10000원 주문 시 1000원을 할인해주고, 20000원 주문 시엔 2000원 할인해주는거에요!
  • 순진 개발자
    • 제가 처음부터 고정 금액 할인은 아니라니까…
  • 악덕 기획자
    • 애자일 소프트웨어 개발 선언 몰라요?
  • 순진 개발자
    • 미치셨습니까 휴먼?
    • 하지만 난 유연한 설계가 가능하도록 객체지향 설계 원칙을 주문했지 후후

순진 개발자가 정말 객체지향 설계 원칙을 잘 준수했는지 확인해보자. 이번에는 주문 금액의 %를 할인해주는 새로운 정률 할인 정책을 추가하자.

RateDiscountPolicy 추가

RateDiscountPolicy

public class RateDiscountPolicy implements DiscountPolicy{

    private final int discountPercent = 10;

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

RateDIscountPolicyTest

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
        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(0);
    }
}
  • static import를 통해 Assertions를 더 편리하게 사용할 수 있음.
  • 좋은 테스트는 실패하는 것도 확인할 수 있어야함.
  • 금액과 관련한 내용들(할인 등)은 굉장히 어려운 로직
    • 실무에서는 경계값 등 수많은 테스트 로직을 포함함.
    • 지금은 설계가 잘 되어있기에 쉽게 테스트 할 수 있는 것

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

새로운 할인 정책 적용

OrderServiceImpl

//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
  • 할인 정책을 변경하려면 클라이언트인 OrderServiceImpl의 코드를 고쳐야 한다.

문제점 발견

  • 우리는 역할과 구현을 충실하게 분리했다.
  • 다형성도 활용하고, 인터페이스와 구현 객체를 분리했다.
  • OCP, DIP와 같은 객체지향 설계 원칙을 충실히 준수한 것 처럼 보이지만 아니다.
    • DIP
      • 주문 서비스 클라이언트(OrderServiceImp)은 DiscountPolicy인터페이스에 의존하면서 DIP를 지킨 것 같이 보일 뿐.
      • 클래스 의존관계를 확인하면, 추상(인터페이스) 뿐만 아니라 구현 클래스에도 의존하고 있다.
        • 추상(Interface) 의존: DiscountPolicy
        • 구체(구현) 클래스: FixDiscountPolicy, RateDiscountPolicy
    • OCP
      • 변경하지 않고 확장할 수 없음
      • 지금 코드를 기능을 확장하여 변경하면 클라이언트 코드에 영향을 준다.
      • 따라서 OCP를 위한한다.

왜 클라이언트 코드를 변경해야 할까?

클래스 다이어그램으로 의존 관계를 분석해보자

기대했던 의존 관계

  • 지금까지 단순히 DiscountPolicy 인터페이스만 의존한다고 생각했다.

실제 의존관계

  • 잘 보면 클라이언트인 OrderServiceImplDiscountPolicy인터페이스 뿐만 아니라 FixDiscountPolicy인 구체 클래스도 함께 의존하고 있다.
  • DIP 위반!

정책을 변경하면?

  • !중요! 그래서 FixDiscountPolicyRateDiscountPolicy로 변경하는 순간 OrderServiceImpl의 소스코드도 함께 변경해야 한다.
  • OCP 위반!

해결방법

  • 클라이언트 코드인 OrderServiceImplDiscountPolicy의 인터페이스 뿐만 아니라 구체 클래스도 함께 의존한다.
  • 그래서 구체 클래스를 변경할 때 클라이언트 코드도 함께 변경해야한다.
  • DIP 위반 → 추상에만 의존하도록 변경 (인터페이스에만 의존)
  • DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경하면 된다.

인터페이스에만 의존하도록 설계를 변경하자!

OrderServiceImpl

private DiscountPolicy discountPolicy;
  • 와! 인터페이스에만 의존하는구나!
  • 는 무슨 NPE 바로 발생함.

해결방안

  • 누군가 클라이언트인 OrderServiceImplDiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다.
profile
더 나은 성취

0개의 댓글