할인 정책 개발 - 예제2

이연희·2022년 1월 27일
0

Spring

목록 보기
28/105

새로운 할인 정책 확장

회원 등급별 할인 정책 적용

  • RateDiscountPolicy.java
  • 기존의 고정 할인 정책과 다른 비율 할인 정책 추가
  • 비율 할인 정책: 10%
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;
        }
    }
}

등급별 할인 정책 적용 확인 테스트 코드

  • @DisplayName: 테스트 통과시 표시 텍스트
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() {
        //given
        Member member = new Member(2L, "memberBasic", Grade.Basic);
        //when
        int discount = discountPolicy.discount(member, 10000);
        //then
        Assertions.assertThat(discount).isEqualTo(1000);
    }
}


할인 정책 변경 과정

  • 할인 정책을 변경하려면 아래 코드와 같이 클라이언트인 OrderServiceImpl 코드를 고쳐야 한다.
public class OrderServiceImpl implements OrderService {
  //    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
      private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    }

문제점 발견

  • 역할과 구현을 분리(Ok)
  • 다형성을 활용하고, 인터페이스와 구현 객체 분리(Ok)
  • OCP, DIP 같은 객체지향 설계 원칙을 준수(Not Ok)
    -> 그렇게 보이지만 사실은 아니다.

❓ DIP: 주문서비스 클라이언트(OrderServiceImpl)는 DiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것이 아닌가?
💡 클래스 의존관계를 살펴보자. 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고 있다. 따라서 DIP를 위반한다.
1) 추상(인터페이스) 의존: DiscountPolicy
2) 구체(구현) 클래스 의존: FixDiscountPolicy, RateDiscountPolicy

❓ OCP: 변경하지 않고 확장할 수 있다고 했는데?
💡 지금까지 코드는 기능을 확장해서 변경하면 클라이언트 코드에 영향을 준다. 따라서 OCP를 위반한다.

  • 기대했던 의존관계는 아래와 같다. 단순히 DiscountPolicty 인터페이스만 의존한다고 생각했다.

  • 하지만 실제 의존관계는 클라이언트 OrderServiceImpl이 DiscountPolicy 인터페이스 뿐만 아니라 FixDiscountPolicy 구체 클래스도 함께 의존한다. 따라서 DIP를 위반한다.

  • 따라서 정책 변경시(FixDiscountPolicy -> RateDiscountPolicy) OrderServiceImpl의 클라이언트 소스 코드를 함께 변경해야 한다. 결국 OCP를 위반한다.

해결방안

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

    ❗구현체가 없기 때문에 아래 코드 실제 실행시 NPE(Null Pointer Exception)이 발생한다.
    💡 누군가 클라이언트 OrderServiceImpl에 DiscountPolicy 구현 객체를 대신 생성하고 주입해주어야 한다.
    💡 AppConfig: 일종의 공연 기획자, 구현 객체를 생성하고 연결하는 책임을 가지는 별도의 설정 클래스

  • 실제 동작에 필요한 구현 객체 생성: MemberServiceImpl, MemoryMemberRepository, OrderServiceImpl, FixDiscountPolicy

  • AppConfig는 생성한 객체 인스턴스의 참조를 생성자를 통해서 주입(연결)

  • MemberServiceImpl -> MemoryMemberRepository

public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}
  • OrderServiceImpl -> MemoryMemberRepository, FixDiscountPolicy
public class OrderServiceImpl implements OrderService {

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

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}
@Configuration
public class AppConfig {//애플리케이션에 대한 환경 설정을 다 해주는 역할, ex) 공연 기획자
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(MemberRepository());//생성자주입: 생성자를 통해 객체 주입
    }

    @Bean
    public MemoryMemberRepository MemberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(MemberRepository(), discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}

💡 클라이언트 코드(MemberServiceImpl, OrderServiceImpl)는 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중 가능
💡 클라이언트인 memberServiceImpl 입장에서 의존관계를 외부에서 주입해주는 것과 같다하여 DI(Dependency Injection)이라 한다. 의존관계 주입 또는 의존성 주입 이라 한다.

  • 클래스 다이어그램

  • 객체의 생성과 연결은 AppConfig가 담당

  • DIP: MemberServiceImpl은 MemberRepository 추상에만 의존하고 구체 클래스는 몰라도 된다.

  • FixDiscountPolicy를 FixDiscountPolicy구성 영역만 영향을 받고 사용 영역은 전혀 영향을 받지 않는다.

profile
공부기록

0개의 댓글