본문 바로가기
카테고리 없음

[프로그래머스]SpringBoot 주문시스템1

by seeder 2023. 7. 14.
728x90

하고자 하는 것은 주문 시스템을 구현하는 것이다. 

 

maven으로 프로젝트를 구성을 하고, Order 클래스(엔티티)를 만들어준다. UUID를 import 하여 진행을 한다. 5개 식별자를 선언한다. 

private final UUID orderid; //식별자를 만듦. 주로 UUID로 만든다.
private final UUID customerid;//주문자
private final List<OrderItem> orderItem;//오더아이템 목록
private FixedAmountVoucher fixedAmountVoucher;//할인 받은 금액
private OrderStatus orderStatus;//오더 상태

FixedAmountVoucher는 원래 단순 할인 금액을 나타내는 discountAmount였으나, 클래스를 따로 구분하여 구현했다. 

 

이로 인한 생성자는 다음과 같다. 

public Order(UUID orderid, UUID customerid, List<OrderItem> orderItem, long discountAmount) {
this.orderid = orderid;
this.customerid = customerid;
this.orderItem = orderItem;
this.fixedAmountVoucher = new FixedAmountVoucher(discountAmount);//인자 받아서 생성해줘야함.
}

그럼 결제하고자 하는 총액은 어떻게 구하나? 사고자 하는 제품 가격*사고자 하는 개수에 할인금액을 빼준다.

public long totalAmount(){
var beforeDiscount = orderItem.stream().map(v->v.getProductPrice()*v.getQuantity())
.reduce(0L, Long::sum);//reduce 메소드는 엘리먼트를 비교하고 컬렉션에서 하나의 값으로 연산한다.
return fixedAmountVoucher.discount(beforeDiscount);
//beforeDiscount-discountAmount 에서, 위처럼 보내면 돼.
}

추가로 orderStatus에 대한 setter도 구현한다.

public void setOrderStatus(OrderStatus orderStatus) {
this.orderStatus = orderStatus;
}

 

**OrderItem,.java

OrderItem은 List 형식이기 때문에 따로 클래스를 만들어 구현한다. 

public final UUID productId;
public final long productPrice;//가격
public final long quantity; //수량

위와 같이 구현하고 생성자와 getter를 구현한다. 

 

**OrderStatus.java

이 클래스는 enum으로 구현한다. 주문 상태에 대한 여러 가지 가능성들을 나열한다.

public enum OrderStatus {//열거형
ACCEPTED,
PAYMENT_REQUIRED,
PAYMENT_CONFIRMED,
PAYMENT_REJECTED,
READY_FOR_DELIVERY,
SHIPPED,
SETTELED,
CANCELLED
}

**FixedAmountVoucher.java

private final long amount;

이름에서 유추하듯 고정된 할인 금액을 갖고 있으며, 생성자, 그리고 할인 전 금액을 받아 할인을 적용시키는 discount 메서드를 구현한다.

public long discount(long beforeDiscount){
return beforeDiscount - amount;
}

 

**OrderTester.java 

->지금까지 구현한 것이 정상작동하는지 확인한다.

 

//제대로 역할 하는지 확인 -> fixedAmountVaucher
var customerId= UUID.randomUUID();
var orderItems = new ArrayList<OrderItem>(){{

add(new OrderItem(UUID.randomUUID(), 100L, 1));
//100원짜리 한개. 물품을 추가해준거죠?
}};
var order = new Order(UUID.randomUUID(), customerId, orderItems, 20L);
//w제품 아이디는 랜덤하게, 할인 금액은 20원으로 적용
Assert.isTrue(order.totalAmount()==80L, MessageFormat.format("total Amount : {0}", order.totalAmount()));
//Assert문으로 확인한다.

 

**여기까지 하고 나서, fixed는 말처럼 정해진 금액만 적용해서 빼주는데, 퍼센트 할인도 적용하려 한다. 각각 만들기보단, 하나의 인터페이스를 두고 implements 하는 게 좋겠다.

 

**Voucher (interface)

public interface Voucher {
UUID getVoucherID();

long discount(long beforeDiscount);
}

상기 코드로 fixed에도 변화가 필요한데, 그것은 voucherId이다. 

private final UUID voucherId;

생성자와 getter도 생성한다.

 

**PercentDiscountVoucher

@Override
public long discount(long beforeDiscount) {

return beforeDiscount*(percent/100);
}

**Order.java에서

private Voucher voucher;//할인 받은 금액

이렇게 적용을 할 수가 있다.

 

 

***IOC 제어의 역전 : 객체가 스스로 생성 X, 프레임워크가 제어 권한을 갖고 있다.

**

OrderService : Order에 대한 비즈니스 로직을 담음. -> 위 그림에서 보듯, 오더 레포지토리와 바우처서비스를 사용한다. 이렇게 함으로써 오더를 하나 새로 만들게 된다. 

private final VoucherService voucherService;
private final OrderRepository orderRepository;

위의 그림대로 두 가지 클래스와 연관된다. 생성자도 만들어주고, 가장 중요한 오더 생성 코드도 구현을 해준다.

public Order createOrder(UUID customerID, List<OrderItem> orderItems,UUID voucherId){
var voucher = voucherService.getVoucher(voucherId);
var order = new Order(UUID.randomUUID(), customerID, orderItems, voucher);
orderRepository.insert(order);//새로 저장, 추가해준거죠?
voucherService.useVoucher(voucher);//재사용 못하게 하는 로직
return order;
}

public Order createOrder(UUID customerID, List<OrderItem> orderItems){
var order = new Order(UUID.randomUUID(), customerID, orderItems);
orderRepository.insert(order);//새로 저장, 추가해준거죠?
return order;
}

 조금 복잡하지만, 여기서 관건은 바우처가 없을 때가 있을 수 있기 때문에(바우처=할인 여부 및 정보로 여김) 바우처를 전달받지 않고도 오더를 생성해야 한다.

 

 나중에 쓰이겠지만, 만든 오더는 

orderRepository.insert(order);//새로 저장, 추가해준거죠?

이렇게 저장해 놓는다.

 

 

OrdeContext : 애플리케이션의 주요 객체에 대한 생성과 관계설정 : 위 그림처럼 오더 서비스, 바우처 서비스, 오더 레포지토리와 관계를 지닌다. 

 

바우처 서비스를 생성하려면, 바우처 레포지토리를 인자로 전달해야 한다. 고로

public VoucherService voucherService(){//voucherRepo를 받아 생성
//되는 친구니 vRepo를 넣어야겠죠?
return new VoucherService(voucherRepository());
}
public VoucherRepository voucherRepository(){
return new VoucherRepository() {
@Override
public Optional<Voucher> findById(UUID voucherId) {

return Optional.empty();
}

};
}

이렇게 할 수가 있고, 

 

오더 서비스는 바우처 서비스와 오더 레포를 전달해 줘야 하기에

public OrderRepository orderRepository(){
return new OrderRepository() {
@Override
public void insert(Order order) {


}
};
}
public OrderService orderService(){
return new OrderService(voucherService(), orderRepository());
}

 이런 식으로 구현한다.

 

**Application Context == IOC Container(IoC 컨테이너는 객체에 대한 생성과 조합이 가능하게 하는 프레임워크)

개별 객체들의 의존이 자동 생성. 레지스터를 하면 의존 관계를 만들고 인스턴스를 만들어준다. appcontext라는 인터페이스가 있다.

 

 ApplicaitonContext는 BeanFactory를 상속하는데 객체에 대 한 생성, 조합, 의존관계설정 등을 제어하는 IoC 기본기능을 BeanFactory에 담당한다. 

 

 Bean은 IoC Container에 의해 관리되는 객체를 말합니다.  Bean : 정의 방법-> annotation 기반. IOC 컨테이너에서 관리되는 객체가 빈. 빈 정보 -> 설정 메타데이터로부터 받아온다.  

 

스프링의 ApplicaitonContext는 실제 만들어야 할 빈 정보를 Configuration Metadata (설정 메타데이터)로부터 받아온다. 이 메타데이터를 이용해서 IoC 컨테이너에 의해 관리되는 객체들을 생성하고 구성한다.

 

Configuration Metadata를 XML 기반으로 하거나 Java 파일 기반으로 작성할 수 있다. 

 

**OrderContext->AppConfiguration

@Configuration//설정 클래스가 됨.
public class AppConfiguration{}

내부 메서드들은 @bean 빈 annotation을 붙인다.

 

**OrderTester

var applicationContext = new AnnotationConfigApplicationContext(AppConfiguration.class);

AnnotationConfigApplicationContext 클래스 인스턴스를 가져오고, AppConfiguration.class는 애플리케이션 콘텍스트에 대한 bean 및 기타 설정을 정의하는 구성 클래스를 지정한다.

var orderService = applicationContext.getBean(OrderService.class);

OrderServiceBean은 메서드를 사용하여 애플리케이션 콘텍스트에서 검색되며,  getBean(). 인수 OrderService.class는 검색할 Bean의 유형을 지정한다.

 

728x90

댓글