본문 바로가기
개인 프로젝트/kiosk-project

Builder pattern<3>

by pon9 2024. 11. 24.

키오스크에 빌더 패턴을 쓰게 된 계기는 예전에 디자인패턴에 관해 훑어볼 때 누군가가 빌더를 서브웨이에 비유한 것을 보고 영감을 얻었다.

https://refactoring.guru/ko/design-patterns/builder

 

빌더 패턴

/ 디자인 패턴들 / 생성 패턴 빌더 패턴 다음 이름으로도 불립니다: Builder 의도 빌더는 복잡한 객체들을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴입니다. 이 패턴을 사용하면 같은 제작

refactoring.guru

이 글을 많이 참고했다. 빌더는 인터페이스와도 잘 쓰인다고 한다. 
나는 한 명의 사용자에게 일관된 로직으로 주문을 받으니 인터페이스를 구현하지는 않고, 빌더클래스를 만들어서 주문정보를 합해서 완성된 Order 객체로 반환하도록 했다.

나는 지금 팩토리랑 빌더를 같이 쓰지만, 팩토리 메서드 패턴으로 시작해 빌더로 발전시키는게 일반적이다.

내 코드에는 둘 다 잘 어울릴 것 같은데.. 써보고싶은데..
지금 나는 객체 생성을 캡슐화 하는 정도로만 단순 팩토리 패턴을 사용하고 있어서 상관없을거다.

일단, Cart(장바구니) 클래스의 기능들이 OrderBuilder 에서 구현하고자 하는 것과 많이 겹쳐서 cart는 삭제하고 코드들을 옮겼다.
orderbuilder가 장바구니에 들어가있는 정보 반환을 담당하고, order은 완성된 주문정보를 반환하도록 만들어보자.

package Order;

import Menu.Main.MainMenuItem;
import Menu.Side.SideMenuItem;

import java.util.List;

public class Order {

    private final List<MainMenuItem> mainItem;
    private final List<SideMenuItem> sideItem;
    private final double totalPrice;

    public Order(List<MainMenuItem> mainItem, List<SideMenuItem> sideItem, double totalPrice) {
        this.mainItem = mainItem;
        this.sideItem = sideItem;
        this.totalPrice = totalPrice;
    }

    public void displayOrder(){
        System.out.println("주문 내역:");
        for(MainMenuItem item : mainItem){
            item.displayMain();
        }
        for(SideMenuItem item : sideItem){
            item.displaySide();
        }
        System.out.println("총 금액: W" + totalPrice);
    }

    public double getTotalPrice() {
        return totalPrice;
    }
}

먼저, 완성된 객체를 반환하는 order패턴이다. 주문이 완료된 이후에 사용되는 클래스다. 영수증 역할이라고 보면 된다.

간단하니까 주석은 생략한다

 

package Order;

import Exception.BadInputException;
import Menu.Main.MainMenuFactory;
import Menu.Main.MainMenuItem;
import Menu.Main.MainMenuType;
import Menu.Side.SideMenuFactory;
import Menu.Side.SideMenuItem;
import Menu.Side.SideMenuType;

import java.util.ArrayList;
import java.util.List;

public class OrderBuilder {

    private static final List<MainMenuItem> mainItem = new ArrayList<>();
    private static final List<SideMenuItem> sideItem = new ArrayList<>();
    private Coupon coupon;
	
    //장바구니가 비었는지를 반환하는 메서드
    public boolean isEmpty() {
        return mainItem.isEmpty() && sideItem.isEmpty();
    }
	
    //메인메뉴 추가 메서드. type과 taste가 같으면 원래 있던것에 수량을 올린다
    public void addMain(MainMenuType type, int quantity, int taste){
        for(MainMenuItem item : mainItem){
            if(item.getType() == type && item.getTaste() == taste){
                item.setQuantity(item.getQuantity() + quantity);
                return;
            }
        }
        mainItem.add(MainMenuFactory.createMainMenu(type, quantity, taste));
    }

	//사이드 추가 메서드. 이것 역시 같으면 수량을 올리고,
    //메인메뉴 없이 주문할 수 없는것과 최대 주문 가능 수량 예외처리를 했다.
    public void addSide(SideMenuType type, int quantity){
        if(mainItem.isEmpty()){
            throw new BadInputException("메인 메뉴 없이 주문할 수 없습니다.");
        }
        for(SideMenuItem item : sideItem){
            if(item.getType() == type){
                if(quantity > type.getMaxQuantity()){
                    throw new BadInputException("최대 주문 가능 수량을 초과했습니다.");
                }
                item.setQuantity(item.getQuantity() + quantity);
                return;
            }
        }
        sideItem.add(SideMenuFactory.createSideMenu(type, quantity));
    }
	
    //쿠폰을 적용하게 된다면 이 setter을 사용한다.
    public void setCoupon(Coupon coupon){
        this.coupon = coupon;
    }

	//쿠폰이 적용되지 않은 총 금액을 우선 계산하는 메서드
    public double totalPrice(){
        return mainItem.stream().mapToDouble(MainMenuItem::getTotalPrice).sum()
                + sideItem.stream().mapToDouble(SideMenuItem::getTotalPrice).sum();
    }

	//쿠폰을 적용한 총금액. 쿠폰이 null이면 totalPrice를 return
    public double couponPrice(){
        if(coupon == null) return totalPrice();
        return coupon.applyCoupon(totalPrice());
    }

	//장바구니 ui
    public void displayBuilder(){
        System.out.println("장바구니:");
        for(MainMenuItem item : mainItem){
            item.displayMain();
        }
        for(SideMenuItem item : sideItem){
            item.displaySide();
        }
        if(coupon == null){
            System.out.println("총 금액: W" + totalPrice());
        }else{
            System.out.println(coupon.getCouponName() + "을 적용한 총 금액: W" + totalPrice());
        }
    }
	
	//최종 빌드(핵심)
    public Order build(){
        return new Order(new ArrayList<>(mainItem), new ArrayList<>(sideItem), couponPrice());
    }
}

Order 객체를 단계별로 build 해주는 Orderbuilder다. 장바구니 역할을 한다.
핵심은 Order build() 메서드에 있는데, "최종 주문 직전" 에 항상 초기화된 빌드값을 반환한다.
그렇기때문에 각종 주문 예외 처리, 쿠폰 적용이 이곳에서 모두 실행된다. 
build메서드를 통해서 최종값만 전달하기 때문에, 이걸 서버에 적용시키면 클라이언트가 빌드 과정을 모르게 숨길 수 있다.
 
상태 패턴도 그렇고 이것 또한 굉장히 쓸 일이 많을 듯한 패턴이다. 인터페이스를 응용하면 복잡한 객체들도 동일한 로직으로 구현할 수 있다. 지금 키오스크의 구성요소가 단순해서 클래스만으로 구현할 수 있었다.

'개인 프로젝트 > kiosk-project' 카테고리의 다른 글

Trouble shooting <4>  (0) 2024.11.25
State pattern <2>  (0) 2024.11.24
kiosk 구조 짜기 <1>  (0) 2024.11.23