백엔드 부트캠프/TIL

[내일배움캠프Spring-16일차] Kiosk 기능 구현

sintory-04 2025. 3. 11. 19:42

- 이전에는 4단계 까지 풀었었으나, 지금은 나머지 5단계와 도전기능을 구현했다.

👉 이전 블로그 보기

- 현재는 리팩토링 전단계로, 내일은 리팩토링, 코드리뷰와 Readme 작성을 할 예정이다.

- 오늘은 도전과제 기능 구현을 정리해보고자 한다.

    1. 도전과제 1단계

    - 장바구니라는 객체를 만드는 부분이다 !

    • 리스트와 리스트 안의 객체를 별도로 생각하여 각각의 클래스 생성하여 카트와 카트 아이템이라는 객체를 만들었다.

    1) 장바구니 생성 및 관리 기능

    ✅ 사용자가 선택한 메뉴를 장바구니에 추가할 수 있는 기능을 제공

    public void addCartItems(MenuItem menuItem){
        CartItem c = new CartItem(menuItem.getMenuName(), menuItem.getMenuPrice(),menuItem.getMenuDescription());
        boolean isAddable = true;
        if(cartItems.isEmpty()){
          cartItems.add(c);
          Output.printfStringOutput(c.getCartItemName()," 해당 음식이 장바구니에 추가 되었습니다.");
          isAddable = false;
        }
        else {
          for (CartItem items : cartItems){
            String current = items.getCartItemName().trim();
            if (current.equalsIgnoreCase(menuItem.getMenuName().trim())) {
              items.addCartItemQuantity();
              Output.printfStringOutput(c.getCartItemName(),"수량이 추가 되었습니다.");
              isAddable = false;
            }
          }
        }
        if (isAddable){
          cartItems.add(c);
          Output.printfStringOutput(c.getCartItemName()," 해당 음식이 장바구니에 추가 되었습니다.");
        }
        Output.printOutput("메뉴판으로 돌아갑니다. \n");
      }

    - CartItem 이라는 객체를 생성해서 각각에 MenuNameMenuPrice, MenuDescription 을 넣어주었다.

    - isAddable 이라는 boolean 변수를 통해서 if 로직을 담당했다.

    - 이런식의 로직을 구현해야지 적절하게 장바구니 아이템이 추가되었다 !

    2) 장바구니 출력 및 금액 계산

    ✅ 각 메뉴의 이름, 가격, 수량, 총 금액 합계 출력

    public void showCartItems(){
        Output.printLineDivider();
        Output.printOutput("아래와 같이 주문하겠습니까? \n");
        Output.printOutput("[Orders]");
        int sum = 0;
        for (CartItem items : cartItems) {
          items.showAllCartItems();
          sum += items.getCartItemPrice() * items.getCartItemQuantity();
        }
        Output.printOutput("\n[Total]");
        Output.printOutput(Integer.toString(sum));
        Output.printLineDivider();
      }
    // CartItem.java
    public void showAllCartItems(){
        // Orders
        Output.printOutput("[총 수량: " + getCartItemQuantity() + " ] " +getCartItemName() + " | " + getCartItemPrice() + " 원 | " + getCartItemDescription());
    }

    - CartItem.java 에서는 해당 값들을 보여주는 함수를 구현했고, 이 함수를 Cart.java 에 가져와서 보여주는 식으로 했다.

    - 반복문을 통해서 장바구니 아이템들과 총수량, 가격을 적절히 계산해서 구현했다.

    3) 장바구니 담기 기능

    ✅ 메뉴를 클릭하면 장바구니에 추가할 지 물어보고, 입력값에 따라 “추가”, “취소” 처리

    public void subMenuLoop(Menu menu, Cart cart) {
        boolean isSubLoopRunning = true;
        while (isSubLoopRunning) {
          int orderMenuItem;
          try {
            // Back or print Menu List
            orderMenuItem = Input.getInput();
            if (orderMenuItem == 0) {
              Output.printLineDivider();
              Output.printOutput("메인 메뉴로 이동합니다. \n");
            } else {
              menu.displaySelectedMenu(orderMenuItem - 1);
              Output.printOutput("위 메뉴를 장바구니에 추가하시겠습니까?" +
                                  "\n1. 확인        | 2. 취소");
              int categoryStatus = Input.getInput();
              switch (categoryStatus) {
                case 1 -> {
                  // Show Choice Menu
                  cart.addCartItems(getSpecificMenuItem(menu, orderMenuItem - 1));
                  isSubLoopRunning = false;
                }
                case 2 -> {
                  Output.printOutput("장바구니에 담지 않았습니다. \n[ Main Menu ] 로 돌아갑니다.\n");
                  isSubLoopRunning = false;
                }
                default -> throw new IndexOutOfBoundsException();
              }
            }
          }
          catch (IndexOutOfBoundsException e) {// Range Error
            Output.printOutput("번호 내에서 입력부탁드립니다. \n[ Main Menu ] 로 돌아갑니다.\n");
            break;
          } catch (RuntimeException e) { // Input Error - Print error message in input.java
            Output.printOutput("[ Main Menu ] 로 돌아갑니다.\n ");
            break;
          }
        }
      }

    - 해당 로직은 다소,, 복잡하게 되어있다. 이거는 내일 리팩토링을 통해서 조금 수정할 예정이다. => 이중 스위치문을 선택할 예정이다.

    4) 주문 기능

    ✅ 장바구니에 담긴 모든 항목을 출력합니다. 합산하여 총 금액을 계산하고, “주문하기”를 누르면 장바구니를 초기화

    public void confirmOrReturn(Cart cart) {
        if (!cart.cartItemsNotEmpty()) {
          throw new IndexOutOfBoundsException();
        }
        cart.showCartItems();
        Output.printOutput("1. 주문하기     | 2. 메뉴판으로 돌아가기     | 3. 장바구니 메뉴 삭제");
        int orderConfirmation = Input.getInput();
        switch (orderConfirmation) {
          case 1 -> {
            Output.printfStringOutput("주문이 완료되었습니다.", "총 금액: " + determineDiscount(cart));
            cart.clearCartItems();
            Output.printOutput("메뉴판으로 돌아갑니다. \n");
          }
          case 2 -> System.out.println("메뉴판으로 돌아갑니다.");
          case 3 -> {
            // 해당 값을 삭제하는 함수 호출.
            Output.printOutput("삭제할 장바구니 메뉴를 입력해주세요. ");
            String text = Input.getString();
            cart.removeCartItems(text.trim());
          }
          default -> throw new IndexOutOfBoundsException();
        }
      }

    - 해당 로직은 비교적 단순한 거 같다. 위에 함수도 이와 같이, try 문을 없애는게 나을 거 같다.

    2. 도전과제 2단계

    1) Enum을 활용한 사용자 유형별 할인율 관리하기

    ✅ 사용자 유형의 Enum 정의 및 각 사용자 유형에 따른 할인율 적용

    public enum Discount {
        GENERAL(4,1.0),
        STUDENT(3,0.97),
        SOLDIER(2,0.95),
        Veteran(1,0.9);
    
        private final int type;
        private final Double rate;
    
        Discount(int type, Double rate) {
          this.type = type;
          this.rate = rate;
        }

    ✅ 주문 시, 사용자 유형에 맞는 할인율 적용해 총 금액 계산

        public static int getDiscountPrice(int price, int userType){
          Discount a = Arrays.stream(values()) // 열거 타입의 모든 열거 객체들을 배열로 만들어 리턴
                  .filter(discount -> discount.type == (userType))
                  .findAny().orElseThrow(); // Error: NoSuchElementException
          return (int) (a.rate * price) ;
        }

    2) 람다 & 스트림을 활용한 장바구니 조회 기능

    ✅ 기존에 생성한 Menu의 MenuItem을 조회 할 때 스트림을 사용하여 출력

      public void showMenuItems(){
        AtomicInteger index = new AtomicInteger(1);
        menuItems.stream()
                .map(item -> String.format("%d. %-25s | %s원 | %s",index.getAndIncrement(),item.getMenuName(),item.getMenuPrice(),item.getMenuDescription()))
                .forEach(System.out::println);
      }

    - 처음에는 index 라는 변수를 만들어서 map 안에 값을 넣어 ++ 하는 식으로 처리할려고 했다. 그러나, 그게 안되지 않는가..! 람다 내에서는 지역 변수는 final 혹은 effectively final이어야 하기 때문에 에러가 나는 것이다. 직접적인 ++ 변경은 불가능했다.

    - 이럴 때에는 두가지 방법이 있다. 첫 번째는 IntStream.range() 라는 배열을 이용해서 i 값을 적절히 사용하던가 ?

    - 두 번째는 AtomicInteger 를 이용해서 변경가능한 객체를 넣던가 였다.

    - 나는 코드 간결화를 위해서 AtomicInteger를 통해서 값을 1로 두고 계속해서 increment 할 수 있게 했다.

    ✅ 기존 장바구니에서 특정 메뉴 빼기 기능을 통한 스트림 활용

    public void removeCartItems(String text){
        if (!cartItemsNotEmpty()) {
          Output.printOutput("장바구니가 비어 있었습니다. \n");
          throw new RuntimeException();
        } else {
          // 카트에 템이 있으면 그걸 비교해서 없애겠다.
          try{
            CartItem find = cartItems.stream()
                    .filter(cartItem -> cartItem.getCartItemName().equals(text)).findAny().orElseThrow();
            cartItems.remove(find);
            Output.printOutput(text+"가 장바구니에서 삭제되었습니다.\n[ Main Menu ] 로 돌아갑니다. \n");
          } catch (Exception e) {
            Output.printOutput("장바구니에 있는 메뉴를 입력하셔야 합니다. \n[ Main Menu ] 로 돌아갑니다. \n");
          }
    
        }
      }

    - remove 는 쉬웠다.

    - 그냥 입력값과 같은 친구를 찾은 후 찾은 객체를 remove 해주면 됐다 !


    ✅ 오늘의 회고

    - 오늘은 마지막 문제까지 풀이하는 게 목표였는데, 적절히 잘 수행한 거 같다.

    - 다소 졸려서 강의를 제대로 듣지 못해서.. 내일은 강의도 따로 복습할 예정이다.

    - 이렇게 말하다보니 내일 할 게 많아진 거 같다. 일단 코드 리팩토링(try 문 간결화, 이중 스위치문 사용), 코드리뷰, Readme 작성을 할 예정이다. 그리고 추가적으로 강의 듣기를 할 것이다 !