본 프로젝트 자료는 김영한님 스프링 핵심 원리 - 기본편을 참고로 제작됐습니다.
현재 상황은 VIP한테 고정으로 무엇을 주문하든 고정으로 1000원을 할인하고자 하는 정책을 실행하고자 하나, 확정이 아니라 언제든 변경될 수 있는 상황이다.
현재 확정된 사항이 없기 때문에 일단 주문이 들어온대로 설계를 진행하되, 추후 DB나 정률 할인 정책으로 바뀔 수 있기 때문에 상황을 대비하여 제작해야 한다.
package hello.core.discount;
import hello.core.member.Member;
public interface DiscountPolicy {
// @return 할인 대상 금액
int discount(Member member, int price);
}
기본 할인 인터페이스 제작
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
public class FixDiscountPolicy implements DiscountPolicy {
// VIP 한정 상시 1000원 할인
private int discountFixAmount = 1000;
// VIP 면 1000원 아니면 0원
@Override
public int discount(Member member, int price) {
if(member.getGrade() == Grade.VIP) {
return discountFixAmount;
} else {
return 0;
}
}
}
VIP면 1000원 고정 할인, else 아니면 할인 0원
package hello.core.order;
public class Order {
private Long memberId;
private String itemName;
private int itemPirce;
private int discountPrice;
public Order(Long memberId, String itemName, int itemPirce, int discountPrice) {
this.memberId = memberId;
this.itemName = itemName;
this.itemPirce = itemPirce;
this.discountPrice = discountPrice;
}
// 상품 금액 빼기 1000원 금액
public int calculatePrice() {
return itemPirce - discountPrice;
}
public Long getMemberId() {
return memberId;
}
public void setMemberId(Long memberId) {
this.memberId = memberId;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public int getItemPirce() {
return itemPirce;
}
public void setItemPirce(int itemPirce) {
this.itemPirce = itemPirce;
}
public int getDiscountPrice() {
return discountPrice;
}
public void setDiscountPrice(int discountPrice) {
this.discountPrice = discountPrice;
}
@Override
public String toString() {
return "Order{" +
"memberId=" + memberId +
", itemName='" + itemName + '\'' +
", itemPirce=" + itemPirce +
", discountPrice=" + discountPrice +
'}';
}
}
package hello.core.order;
public interface OrderService {
// 최종 오더의 결과 값을 반영하는 로직
Order createOrder(Long memberId, String itemName, int itemPrice);
}
package hello.core.order;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
주문 생성 요청이 오면, 회원 정보를 조회하고, 할인 정책을 적용한 다음 주문 객체를 생성해서 반환한다. 메모리 회원 리포지토리와, 고정 금액 할인 정책을 구현체로 생성한다.
public class OrderApp {
public static void main(String[] args) {
MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
System.out.println("order = " + order.toString());
}
}
현재 고객의 주문을 받을 수 없는 상태이기에, 임의로 1번 VIP 손님의 주문과 할인이 제대로 들어갔는지 확인 해본다.
/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=49800:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/joseoghwan/Desktop/study/core/out/production/classes:/Users/joseoghwan/Desktop/study/core/out/production/resources:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter/2.7.12/a5f6a95e4e4a50cfaf0f14c86240d8be49637141/spring-boot-starter-2.7.12.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-autoconfigure/2.7.12/5bb8661bba72f7ca353ae486ccb06285f0ed84eb/spring-boot-autoconfigure-2.7.12.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot/2.7.12/888c3545dc3c6ca791753c7ad621a2d03f222732/spring-boot-2.7.12.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-logging/2.7.12/b6590492b4d92baebb4ac7c1b5db11fc3488e347/spring-boot-starter-logging-2.7.12.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/jakarta.annotation/jakarta.annotation-api/1.3.5/59eb84ee0d616332ff44aba065f3888cf002cd2d/jakarta.annotation-api-1.3.5.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.springframework/spring-core/5.3.27/ff5e35f2d4f72d22c476ff9b7bce1de25c980ebd/spring-core-5.3.27.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/1.30/8fde7fe2586328ac3c68db92045e1c8759125000/snakeyaml-1.30.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.springframework/spring-context/5.3.27/251162aa2fe5cb374a482ae87fa6e8e8e747d72/spring-context-5.3.27.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.12/d4dee19148dccb177a0736eb2027bd195341da78/logback-classic-1.2.12.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-to-slf4j/2.17.2/17dd0fae2747d9a28c67bc9534108823d2376b46/log4j-to-slf4j-2.17.2.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.slf4j/jul-to-slf4j/1.7.36/ed46d81cef9c412a88caef405b58f93a678ff2ca/jul-to-slf4j-1.7.36.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.springframework/spring-jcl/5.3.27/9698ea7d5361b5e3a27ed08beb7770279bd2397/spring-jcl-5.3.27.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.springframework/spring-aop/5.3.27/ecae2962d53c587fd0e405cd60dc8415d1b9e12d/spring-aop-5.3.27.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.springframework/spring-beans/5.3.27/46e7d917551ffcc0a104fd971d1fa207b30d7761/spring-beans-5.3.27.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.springframework/spring-expression/5.3.27/6225619970e8376df5c3d777610d9d03b977063b/spring-expression-5.3.27.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.2.12/1d8e51a698b138065d73baefb4f94531faa323cb/logback-core-1.2.12.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.36/6c62681a2f655b49963a5983b8b0950a6120ae14/slf4j-api-1.7.36.jar:/Users/joseoghwan/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.17.2/f42d6afa111b4dec5d2aea0fe2197240749a4ea6/log4j-api-2.17.2.jar hello.core.OrderApp
order = Order{memberId=1, itemName='itemA', itemPirce=10000, discountPrice=1000}
Process finished with exit code 0
정상적으로 출력되는걸 확인할 수 있다. 하지만 좋은 테스트 방법이 아니라 JUnit 테스트를 사용하자.
package hello.core.test;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class OrderServiceTest {
MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();
@Test
void createOrder() {
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
오류가 발생하지 않는다면 잘 작동된거라 보면 될 듯 싶다. 다음 내용은 현 고정 할인 정책이 아닌 정률 할인 정책으로 수정하며, 좋은 객체 지향 설계의 5가지 원칙을 제대로 활용해보고자 한다.