스프링 핵심 원리 1 - 예제 만들기 2

WooHyeong·2022년 10월 15일

Spring

목록 보기
17/27

"계획을 따르기 보다 변화에 대응하기를"
참고 : 애자일 소프트웨어 개발 선언 https://agilemanifesto.org/iso/ko/manifesto.html

이전 개발의 도메인 설계를 살펴보자.

주문과 할인 도메인 설계

  • 주문과 할인 정책
    - 회원은 상품을 주문할 수 있다.
    • 회원 등급에 따라 할인 정책을 적용할 수 있다.
    • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용한다.(나중에 변경 될 수 있다.)
    • 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했다.(미확정)

이번에는 할인 정책을 변경해보려고 한다. 객체 지향의 원리를 적용하여 새로운 할인 정책을 추가한다.

회원 등급에 따라 주문한 금액의 %를 할인해주는 새로운 정률 할인 정책을 추가하자.

RateDiscountPolicy 코드 추가
package hello2.core2.discount;

import hello2.core2.member.Grade;
import hello2.core2.member.Member;

public class RateDiscountPolicy implements DiscountPolicy {

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

작성한 코드가 정상 작동하는 지 확인하기 위하여 Test를 진행한다.
단축키 : Ctrl + Shilft + t -> create new Test

테스트 작성
package hello2.core2.discount;

import hello2.core2.member.Grade;
import hello2.core2.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

class RateDiscountPolicyTest {

    DiscountPolicy discountPolicy = new RateDiscountPolicy();

    @Test
    @DisplayName("10% discount should be adapted to VIP")
    void vip_o() {

        //given
        Member member = new Member(1L, "memberVIP", Grade.Vip);

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

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

}

할인정책을 추가하고 테스트까지 완료하였다.

새로운 할인 정책을 애플리케이션에 적용해보자.

할인 정책을 변경하기 위해 클라이언트인 OrderServiceImpl 코드를 고친다.

public class OrderServiceImpl implements OrderService {

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

기존의 정액 할인 정책을 주석처리하고, 정률 할인 정책을 추가하였다.

이전에 작성한 OrderServiceTest를 실행하면 정상 작동한다.
그렇다면 우리는 스프링의 SOLID 원칙을 준수하여 잘 작성한걸까?

답은 아니다.

  • 우리는 역할과 구현을 충실하게 분리했다.

  • 다형성도 활용하고, 인터페이스와 구현 객체를 분리했다.

  • OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수했다.
    --> 그렇게 보이지만 그렇지 않다.

  • DIP : 주문서비스 클라이언트(OrderServiceImpl)는 DiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것 같은데?
    --> 클래스 의존관계를 분석하면, 추상(인터페이스)뿐만 아니라 구체(구현) 클래스에도 의존하고 있다.
    - 추상(인터페이스) 의존 : DiscountPolicy
    - 구체(구현) 클래스 : FixDixcountPolicy, RateDiscountPolicy

  • OCP : 변경하지 않고 확장할 수 있다고 하였는데, 새로운 정책을 적용하면서 변경하였던 것을 살필 수 있다.
    --> 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다! 따라서 OCP를 위반한다.

그렇다면 위 문제들을 어떻게 해결할 수 있을까?

  • DIP 위반 -> 추상화에만 의존하도록 변경한다.
  • DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경하면 된다.
인터페이스에만 의존하도록 코드 변경
public class OrderServiceImpl implements OrderService {

    private fin al MemberRepository memberRepository = new MemoryMemberRepository();
    private DiscountPolicy discountPolicy;
    //private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
  • 인터페이스에만 의존하도록 설계와 코드를 변경했다.
  • 이로써, DIP와 OCP가 해결된 것처럼 보인다.
  • 그런데 구현체가 없는데 어떻게 코드를 실행할 수 있을까?
  • 실제 실행을 해보면 NPE(null pointer exception)이 발생한다.
해결방안
  • 이 문제를 해결하려면 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 생성하고 주입해주어야 한다.
profile
화이링~!

0개의 댓글