Skip to main content

우테코 크리스마스 프로모션

·8 mins· loading · loading ·
Soeun Uhm
Author
Soeun Uhm
problem-solving engineer, talented in grit.
Table of Contents

christmas-promotion

🎅🏻 우테코 식당의 크리스마스 프로모션을 소개합니다 🎅🏻

  • 이번 프로모션에서는 중복된 할인과 증정을 허용합니다.
  • 올해 12월에 지난 5년 중 최고의 판매 금액을 달성하는 것이 목표입니다.
  • 12월 이벤트 참여 고객의 5%가 내년 1월 새해 이벤트에 재참여 하는 것이 목표입니다.

🎄 프로모션 소개
#

🍭 메뉴
#

<애피타이저>
양송이수프(6,000), 타파스(5,500), 시저샐러드(8,000)

<메인>
티본스테이크(55,000), 바비큐립(54,000), 해산물파스타(35,000), 크리스마스파스타(25,000)

<디저트>
초코케이크(15,000), 아이스크림(5,000)

<음료>
제로콜라(3,000), 레드와인(60,000), 샴페인(25,000)

📅 할인 달력
#

image

📍 할인 이벤트 정리
#

<크리스마스 D-Day 할인 : 12/1 ~ 12/25>

  • 1,000원으로 시작하여 크리스마스가 다가올수록 날마다 할인 금액이 100원씩 증가
  • 총주문 금액에서 해당 금액만큼 할인

<기타 할인 : 12/1 ~ 12/31>

  • 평일할인 : 디저트메뉴 (초코케이크, 아이스크림) 1개당 2023원 할인
  • 주말할인 : 메인메뉴(티본스테이크, 바비큐립, 해산물파스타, 크리스마스파스타) 1개당 2023원 할인
  • 특별할인 : 총 주문에서 1000원 할인

<증정이벤트 : 12/1 ~ 12/31>

  • 증정이벤트 : 할인 전 총주문 금액이 120,000원 이상일 때 샴페인 1개 증정

🏅 혜택 금액에 따른 12월 이벤트 배지 부여
#

  • 총 혜택 금액 (총 할인 금액 + 증정 메뉴의 가격) 에 따라 이벤트 배지를 부여합니다.
    • 5,000원 이상 : 별
    • 10,000원 이상 : 트리
    • 20,000원 이상 : 산타

❗️이벤트 주의 사항
#

  • 총 주문 금액 10,000원 이상 부터 이벤트가 적용됩니다.
  • 음료만 주문 시, 주문할 수 없습니다.
  • 메뉴는 한 번에 최대 20개까지만 주문 할 수 있습니다.

🧐 예상 시나리오
#

[시나리오1]
  • 방문 날짜 : 3일
  • 주문 메뉴 : 티본스테이크-1,바비큐립-1,초코케이크-2,제로콜라-1
  • 할인 전 총주문 금액 : 55,000 + 54,000 + 15,000 * 2 + 3,000 = 142,000
  • 증정 내역 : 샴페인 1개
  • 혜택 내역
    • 크리스마스 할인 : 1000 + (3-1) * 2 = 1,200원
    • 특별 할인 : 1000원
    • 평일 할인 : 디저트 메뉴(초코케이크) 2개 -> 2,023 * 2 = 4,046원
    • 증정 이벤트 : 25,000원
  • 총혜택 금액 : (1,200 + 1,000 + 4,046 + 25,000) = 31,246원
  • 할인 후 예상 결제 금액 : 할인 전 총주문 금액(142,000) - 총할인 금액(1200 + 1000 + 4046 = 6,246) = 135,574원
  • 이벤트 배지 : 총혜택 금액(31,246원) >= 20,000 이므로 산타
[시나리오2]
  • 방문 날짜 : ( Author 의 생일인) 23일
  • 주문 메뉴 : 양송이수프-2,크리스마스파스타-2,바비큐립-1,레드와인-1
  • 할인 전 총주문 금액 : 6,000 * 2 + 25,000 * 2 + 54,000 + 60,000 = 176,000원
  • 증정 내역 : 샴페인 1개
  • 혜택 내역
    • 크리스마스 할인 : 1000 + (23-1) * 100 = 3,200원
    • 주말 할인 : 메인메뉴(크리스마스파스타 2개, 바비큐립 1개) -> 2,023 * 3 = 6,069원
    • 증정 이벤트 : 25,000원
  • 총혜택 금액 : (3,200 + 6,069 + 25,000) = 34,269원
  • 할인 후 예상 결제 금액 : 할인 전 총주문 금액(176,000) - 총할인 금액(3,200 + 6,069 = 9,269) = 166,731
  • 이벤트 배지 : 총혜택 금액(34,269원) >= 20,000 이므로 산타

🎯 기능 구현 목록
#

✅ 기능
#

  • 방문 날짜를 입력받는 기능
    • 안내 메세지를 출력한다.
    • 방문날짜는 1이상 31 이하의 정수이다.
    • 1이상 31이하의 숫자가 아닐 경우, “[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.” 라는 에러 메세지를 출력한다.
    • 에러 메세지를 출력한 후, 재입력을 받는다.
    • 방문 날짜가 주말인지 확인한다.
    • 방문 날짜가 평일인지 확인한다.
    • 방문 날짜가 크리스마스 이전인지 확인한다.
  • 주문할 메뉴와 개수를 입력 받는 기능
    • 안내 메세지를 출력한다.
    • {메뉴}-{수량} 형식으로 입력 받는다.
    • 메뉴 형식이 위와 다른 경우, “[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.” 라는 에러 메세지를 출력한다.
    • 각 메뉴는 쉼표(,) 로 구분한다.
    • 메뉴판에 없는 메뉴를 입력한 경우, “[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.” 라는 에러 메세지를 출력한다.
    • 메뉴의 개수는 1 이상의 숫자만 입력받는다.
    • 메뉴의 개수가 1 이상의 숫자가 아닐 경우 “[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.” 라는 에러 메세지를 출력한다.
    • 중복 메뉴를 입력한 경우(e.g. 시저샐러드-1,시저샐러드-1), “[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.“라는 에러 메시지를 출력한다.
    • 음료만 주문한 경우, “[ERROR] 음료만 주문할 수 없습니다. 다시 입력해 주세요.” 라는 에러 메세지를 출력한다.
    • 메뉴 수량의 합이 20 초과할 시, “[ERROR] 메뉴는 한 번에 최대 20개까지만 주문할 수 있습니다. 다시 입력해 주세요.” 라는 에러 메세지를 출력한다.
    • 위의 모든 에러 메세지는 출력 후, 재입력을 받는다.
  • “12월 %d일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!” 메세지를 출력한다.
  • 주문된 메뉴를 출력하는 기능
    • 주문 메뉴를 ‘{메뉴이름} {수량}개’ 형식으로 출력한다.
  • 할인 전 총주문 금액을 계산하는 기능
    • 총주문 금액은 각 메뉴 별로 메뉴 수량 * 메뉴 금액 을 더한 값이다.
    • 총주문 금액을 출력한다.
  • 증정 이벤트를 출력하는 기능
    • 할인 전 총주문 금액이 120,000 이상일 시 “샴페인 1개"를 출력한다.
    • 할인 전 총주문 금액이 120,000 미만일 시 “없음” 을 출력한다.
  • 혜택 내역을 출력하는 기능
    • 총주문 금액이 10,000 미만일 시 혜택을 적용하지 않는다.
    • 크리스마스 디데이 할인
      • 방문날짜가 25 이하인 경우에만 적용한다.
      • 할인 금액은 (1000 + (방문 날짜 - 1) * 100 ) 이다.
    • 주말 할인
      • 방문 날짜가 (1,2,8,9,15,16,22,23,29,30) 에 포함되는 경우, 메인 메뉴(티본스테이크, 바비큐립, 해산물파스타, 크리스마스파스타) 수량 1개 당 2,023원 할인한다.
      • e.g. 티본스테이크-2, 바비큐립-1 을 주문했으면, 총 2,023 * 3 = 6,069 원 할인한다.
      • 평일 할인
        • 방문 날짜가 (3,4,5,6,7,10,11,12,13,14,17,18,19,20,21,24,25,26,27,28,31) 에 포함되는 경우, 디저트 메뉴(초코케이크, 아이스크림) 1개당 2,023원 할인한다.
        • e.g. 초코케이크-2, 아이스크림-2, 바비큐립-1 을 주문했으면, 2,023 * 4 = 8,092 원 할인한다.
    • 특별 할인
      • 방문 날짜가 (3,10,17,24,31) 에 포함되는 경우, 총 주문에서 1,000원 할인한다.
    • 증정 이벤트
      • 할인 전 총주문 금액이 120,000원 이상일 시 샴페인을 증정한다.
      • 할인 전 총주문 금액이 120,000원 이상일 시 “증정 이벤트 : -25,000원” 을 출력한다.
    • 혜택 금액은 -{금액}원 형식으로 출력한다.
    • 혜택 내역이 없을 시 “없음” 을 출력한다.
  • 총혜택 금액을 출력하는 기능
    • 할인 전 총주문 금액이 120,000 미만일 시, (총 할인 금액) 을 출력한다.
    • 할인 전 총주문 금액이 120,000 이상일 시, (샴페인 가격(25,000) + 총 할인 금액) 을 출력한다.
  • 할인 후 예상 결제 금액을 출력하는 기능
    • (할인 전 총주문 금액 - 총 할인 금액) 을 계산하여 출력한다.
  • 12월 이벤트 배지 출력 기능
    • 총혜택 금액에 따라 차등으로 출력한다.
      • 총혜택 금액이 5,000원 이상 : 별
      • 총혜택 금액이 10,000원 이상 : 트리
      • 총혜택 금액이 20,000원 이상 : 산타
    • 총 혜택 금액이 5,000원 미만일 시 “없음” 을 출력한다.

⚠️예외
#

  • 입력 형식 관련 예외
    • 입력값이 {메뉴}-{수량} 형식이 아닐 때 발생하는 예외 : MenuFormatException
  • 날짜 관련 예외
    • 날짜가 정수가 아닐 때 발생하는 예외 : DateNotIntegerException
    • 날짜가 1이상 31이하가 아닐 때 발생하는 예외 : DateRangeException
  • 메뉴 관련 예외
    • 메뉴판에 없는 메뉴일때 발생하는 예외 : MenuNotFoundException
    • 메뉴 개수가 1이상이 아닐 때 발생하는 예외 : MenuCountZeroException
    • 중복 메뉴를 입력한 경우 발생하는 예외 : DuplicateMenuException
    • 음료만 주문한 경우 발생하는 예외 : OnlyDrinkException
    • 메뉴 수량이 20개 초과 시 발생하는 예외 : MenuAmountOverLimitException

🧭 프로그램 설계
#

스크린샷 2023-11-15 오후 2 59 59

1. Enum 으로 무엇을 관리할 것인가?

관련된 상수를 Enum 을 이용해서 관리해보라는 피드백이 있었다. 이전 주차에서도 Enum 을 활용하긴 했지만, 이번에는 어떤 상수들을 같이 묶을 지 고민해보았다. 그래서 난 Enum 으로 DateConstant, DiscountConstant, EventBadge, Menu, PromotionConstant 5가지를 관리했다.

  • DateConstant

    이번에는 ‘날짜’ 를 입력값으로 받고, 날짜에 따라 어떤 혜택을 적용할지 선택해야 했다. 따라서 아래와 같이 java 의 내장 함수인 java.time.DayOfWeek, java.time.LocalDate 를 사용해서 2023.12월의 주말/주중을 Set 으로 만들었다.

    MIN_DATE(1),
    MAX_DATE(31),
    XMAS(25);
    
    public static final Set<Integer> WEEKDAYS = new HashSet<>();
    public static final Set<Integer> WEEKENDS = new HashSet<>();
    public static final Set<Integer> SPECIAL_DAYS = new HashSet<>(Arrays.asList(
            3, 10, 17, 24, 25, 31
    ));
    
    static {
        LocalDate start = LocalDate.of(2023, 12, MIN_DATE.date);
        LocalDate end = LocalDate.of(2023, 12, MAX_DATE.date);
    
        for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
            DayOfWeek day = date.getDayOfWeek();
            if (day == DayOfWeek.SATURDAY || day == DayOfWeek.FRIDAY) {
                WEEKENDS.add(date.getDayOfMonth());
                continue;
            }
            WEEKDAYS.add(date.getDayOfMonth());
        }
    }
    
  • DiscountConstant

    할인 관련 상수값을 모았다. 주말/주중 할인 (2023원), 크리스마스 할인 시작 금액(1000원), 증가 금액(100원) 등을 모아두었다.

  • EventBadge

    이벤트 배지의 이름과, 기준 금액을 모아두었다.

  • Menu

    메뉴에 있는 음식들의 카테코리, 이름, 금액을 모아두었다.

  • PromotionConstant

    프로모션 이벤트 이름을 모아두었다.

2. 예외 처리를 어떻게 할 것인가?

이번에는 다른 예외 상황에도 예외 메세지를 통일하라는 요구사항이 있었다. 따라서 Custom Exception 을 만들었다. 지난주에도 Custom Exception 을 만들었었는데, Custom Exception 을 생성하는 비용이 크다고 하여, 이번주에는 Custom Exception 을 생성하는 방법을 바꾸었다.

내 Custom Exception 은 유효하지 않은 값을 입력 받았을 때 하위 비즈니스 로직들을 수행하지 못하기 위한 용도였기 때문에 현재 값이 어떤 call stack 을 가지는지 정보가 필요 없었다. 따라서 아무 trace 도 갖지 않도록 fillinStackTrace() 를 오버라이딩 해주었다. 또한, static final 로 예외를 선언하여 매번 throw new 할 필요 없이, 캐싱하여 사용했다.

3. 재입력을 어떻게 받을 것인가?

저번 주차에는 재귀를 통해 재입력을 받았다. 하지만 입력하는 횟수가 커지면 StackOverFlow 가 발생할 수 있다고 코드 리뷰어님이 제시해주셨다. 따라서, 이번 주차에는 함수형 인터페이스를 통해 해결하였다. Supplier 를 통해 해결하였다.

private Date getDate() {
        return executeWithExceptionHandle(() -> {
            int inputDate = InputView.readDate();
            return Date.of(inputDate);
        });
    }

private MenuCount getMenu() {
        return executeWithExceptionHandle(() -> {
            String input = InputView.readMenu();
            Map<String, Integer> parsedMenu = Parser.parseMenuCount(input);
            return new MenuCount(parsedMenu);
        });
    }

private static <T> T executeWithExceptionHandle(final Supplier<T> supplier) {
        while (true) {
            try {
                return supplier.get();
            } catch (IllegalArgumentException e) {
                System.out.println(e.getMessage());
            }
        }
    }

4. 전략 패턴을 어떻게 쓸 것인가?

전략 패턴은 3가지로 구성된다.

  • 전략 메서드를 가진 전략 객체 : WootecoDiscountStrategy, WootecoBadgeStrategy
  • 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자, 어떤 객체를 핸들링하기 위한 접근 수단) : PromotionService
  • 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(전략 객체의 공급자) : Controller 에서 주입

🧠 고민한 지점
#

View가 Model 에 의존해도 될까?
  • Controller 에서 View 에게 넘겨줄 때, 원시값을 줄 지 Model 값을 줄 지 고민이 되었다.
  • Model 값을 넘겨준다면 View 가 Model 에 의존하게 되고, Model 의 이름이 변경되거나 수정사항이 있을 때 View 의 코드도 바꿔야 하는 문제가 생길 수 있다.
  • 원시값을 넘겨준다면 Controller 에서 코드 중복이 발생하는 문제가 생겼다.
  • 이번 프로그램 설계에서는 Model 값을 넘겨주는 것으로 했다. Controller 의 로직은 훨씬 간편해졌지만, 위의 문제 괜찮을 지는 조금 더 고민해보고 토론해봐야 할 지점이다.
구체적인 전략 주입은 어디에서?
  • 이번 우테코 프로모션에서, 가장 크게 변경될 수 있는 것이 이벤트별 할인 전략이라고 생각했다.
  • 따라서 DiscountStrategy 와, BadgeStrategy 를 만들고, WootecoDiscountStrategy 와 WootecoBadgeStrategy 에서 할인 정책을 구체적으로 구현했다.
  • 이 전략을 실제로 활용해서 총 혜택 금액과 배지 등급을 계산하는 것은 PromotionService 이 하도록 했다.
  • 여기서 Controller 에서 구체적인 전략을 주입해줄지, Application 에서 구체적인 전략을 주입할 지 고민이 되었다.
  • 나의 결론은, input 값에 따라서 전략을 바꿀 상황이 생길 수 있기 때문에 Controller 에서 실제 전략을 주입해 주었다.
  • 주입해주는 방법에는 Config 를 생성해서 주입하는 방법, Strategy Factory 를 생성하는 방법, Setter Injection 등 더 다양한 방법이 있을 것이라 생각한다. 그 중 어느 것이 최선의 방법이 될지는 조금 더 고민을 해봐야겠다.

💭 소감
#

우테코 프리코스가 드디어 끝났다. 마치 애증의 연인과 이별하는 시원섭섭하고 후련한 기분이다. 처음 참여할 때만 해도 하루에 2시간 정도만 투자하자고 생각했었다. 하지만 하면 할 수록 점점 욕심이 나서 마지막 주차에는 학교 과제도 다 제껴두고 프리코스에만 시간을 많이 투자했다..

사실 자바에 입문한지 얼마 되지 않아서 과연 내가 이것을 끝낼 수 있을까? 라는 두려움도 있었다. 그래도 못하면 다른 사람들 코드 보면서 공부하면 되지 !! 라는 생각으로 계속 했다. 생각보다 재밌었고, 생각보다 스트레스도 많이 받았다.

그러나 이 모든 것을 떠나서 4주전과 비교해서 지금의 나는 클린 코드, 리팩토링, Java 문법 전반에 대해 훨씬 더 많이 알고 있다. 이 사실 하나만으로 4주를 허투로 보내지는 않은 것 같아 마음이 놓인다. 앞으로의 결과가 어떻게 되든, 이제 내 손을 떠났으니 다시 학교, 동아리에 집중하고자 한다. 해보고 싶은 프로젝트가 산더미이다. ⛰️