- 프로젝트 생성
- 비즈니스 요구사항과 설계
- 회원 도메인 설계
- 회원 도메인 개발
- 회원 도메인 실행과 테스트
- 주문과 할인 도메인 설계
- 주문과 할인 도메인 개발
- 주문과 할인 도메인 실행과 테스트
주문과 할인 도메인 개발
이전 게시글의 주문과 할인 도메인 설계를 기반으로 개발을 진행한다.
할인 도메인 개발
core의 하위에 discount 패키지를 만든다.
DiscountPolicy (인터페이스)
할인 정책 역할에 대한 인터페이스를 만든다.
할인이 얼마나 들어갈지 금액을 반환한다.
package hello.core.discount;
import hello.core.member.Member;
public interface DisountPolicy {
/**
*
* @param member
* @param price
* @return 할인 대상 금액
*/
int discount(Member member, int price);
}
FixDiscountPolicy
요구사항은 VIP에 대해서만 고정금액으로 할인(1000원)이므로, 고정금액 할인 구현체를 만들어준다.
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
public class FixDiscountPolicy implements DisountPolicy {
private int discountFixAmount = 1000; //고정적으로 VIP은 1000원을 할인
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) { //VIP인 경우에만 fixAmount만큼 할인
return discountFixAmount;
} else {
return 0;
}
}
}
주문 도메인 개발
core의 하위에 order 패키지를 만든다.
Order
주문 객체 엔티티 생성
주문이 생성될 때 필요한 정보인 attribute 4가지를 포함하도록 생성한다.
주문을 생성하고 클라이언트에게 다시 이 "Order" 객체로 반환해주어야 한다.
package hello.core.order;
public class Order {
private Long memberId;//주문 한 고객 id
private String itemName;//주문한 상품명
private int itemPrice;// 주문한 상품 가격
private int discountPrice; // 할인 적용 금액
//생성자 (주문이 들어오고 할인 가격까지 계산되었을 때 객체가 생성된다)
public Order(Long memberId, String itemName, int itemPrice, int discountPrice) {
this.memberId = memberId;
this.itemName = itemName;
this.itemPrice = itemPrice;
this.discountPrice = discountPrice;
}
//최종 가격에 대한 메소드
public int calculatePrice(){
return itemPrice - discountPrice; //최종 가격
}
//getter & setter
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 getItemPrice() {
return itemPrice;
}
public void setItemPrice(int itemPrice) {
this.itemPrice = itemPrice;
}
public int getDiscountPrice() {
return discountPrice;
}
public void setDiscountPrice(int discountPrice) {
this.discountPrice = discountPrice;
}
//객체를 출력하면 자동으로 toString으로 attribute의 값을 알 수 있음
@Override
public String toString() {
return "Order{" +
"memberId=" + memberId +
", itemName='" + itemName + '\'' +
", itemPrice=" + itemPrice +
", discountPrice=" + discountPrice +
'}';
}
}
OrderService 인터페이스
클라이언트가 주문 시 주문을 생성 후 마지막에 반환하는 메소드의 역할을 하도록 인터페이스를 생성한다.
package hello.core.order;
public interface OrderService {
//주문 생성 시 (주문자 id, 아이템 명, 아이템 가격)을 넘겨주기로 했음
Order creatOrder(Long memberId, String itemName, int itemPrice);
}
OrderServiceImpl
인터페이스를 실제 구현체를 이용하여 구현
package hello.core.order;
import hello.core.discount.DisountPolicy;
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();// 구현체로 Memory Repo사용
private final DisountPolicy disountPolicy = new FixDiscountPolicy();// 구현체로 정액할인 사용
@Override
public Order creatOrder(Long memberId, String itemName, int itemPrice) { //1. 주문 생성
// 2. 회원 조회
Member member = memberRepository.findById(memberId);
// 3. 할인 적용
//할인에 대한 내용은 Order에서 신경쓰지 않고 결과만 사용한, discountPolicy에게 일임함 -> 잘 된 설계 : 단일 체계의 원칙이 잘 지켜짐
// 할인에 대한 변경이 들어올 때 OrderService는 한줄도 변화하지 않는 상태임
int discountPrice = disountPolicy.discount(member, itemPrice); //최종적으로 할인된 가격
// 4. 주문 결과 반환
return new Order(memberId, itemName, itemPrice, discountPrice); // 주문 생성하여 반환됨
}
}
이 그림대로 개발된 모습이며,
메모리 회원 저장소와, 정액 할인 정책 구현체를 선택했으므로,
다음과 같이 동적으로 객체(메모리 회원 저장소, 정액 할인정책) 다이어그램이 생성되었다.
주문과 할인 도메인 실행과 테스트
순수 자바 코드로 테스트
core하위에 OrderApp을 생성해준다. (메인메소드에서 직접 실행/클라이언트)
OrderApp
package hello.core;
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;
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.creatOrder(memberId, "itemA",10000);
//생성된 주문 출력
System.out.println("order = " + order );
System.out.println("order Calculate = " + order.calculatePrice()); //지불해야할 금액
}
}
결과는 다음과 같다.
order = Order{memberId=1, itemName='itemA', itemPrice=10000, discountPrice=1000}
order Calculate = 9000
Process finished with exit code 0
Spring 을 이용한 테스트
package hello.core.order;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
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; // long type은 NULL불가 wrapper type인 Long사용
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
//가진 정보를 가지고 주문
Order order = orderService.creatOrder(memberId, "itemA",10000);
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
단위테스트는 중요하다.
스프링 컨테이너의 도움 없이 순수 자바코드로 이루어진 테스트를 잘 짜야한다(이게 훨씬 빠름)
스프링 컨테이너의 도움을 받는 통합 테스트는 속도도 느리다.
의도대로 프로그램이 잘 실행되는 것을 볼 수 있다.
이제 이 코드들이 다형성을 잘 지켜졌는지, 실제로 다른 구현체로 변경해도 유연하게 변경이 가능한지 알아볼 차례다!
'Backend > Spring' 카테고리의 다른 글
[Spring] 순수자바의 문제와 해결 2) 관심사의 분리(SRP/DIP) (0) | 2021.09.23 |
---|---|
[Spring] 순수 자바의 문제와 해결 1) OCP/DIP위반 (0) | 2021.09.22 |
[Spring] 순수 자바 예제 2) - 회원 도메인 개발&테스트 (0) | 2021.09.22 |
[Spring] 순수 자바 예제 1) 회원 & 주문/할인 도메인 설계 (0) | 2021.09.20 |
[Spring] 최종 개념 정리 (0) | 2021.09.19 |