참고
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();
//생략...
}
문제점 발견!
- 우리는 역할과 구현을 충실하게 분리했다 -> OK
- 다형성도 활용하고, 인터페이스와 구현 객체를 분리했다 -> OK
- 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의 구현 객체를 대신 생성하고 주입해줘야 한다!
'Spring > Spring core basic' 카테고리의 다른 글
[Spring core basic] 11 - AppConfig 리팩토링 (0) | 2022.05.02 |
---|---|
[Spring core basic] 10 - 관심사의 분리 (0) | 2022.05.02 |
[Spring core basic] 08 - 주문과 할인 도메인 개발 (0) | 2022.05.02 |
[Spring core basic] 07 - 주문과 할인 도메인 설계 (0) | 2022.05.02 |
[Spring core basic] 06 - 회원 도메인 개발 (0) | 2022.05.02 |
댓글