우테코 크리스마스 프로모션
Table of Contents
🎅🏻 우테코 식당의 크리스마스 프로모션을 소개합니다 🎅🏻
- 이번 프로모션에서는 중복된 할인과 증정을 허용합니다.
- 올해 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)
📅 할인 달력#
📍 할인 이벤트 정리#
<크리스마스 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
🧭 프로그램 설계#
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주를 허투로 보내지는 않은 것 같아 마음이 놓인다. 앞으로의 결과가 어떻게 되든, 이제 내 손을 떠났으니 다시 학교, 동아리에 집중하고자 한다. 해보고 싶은 프로젝트가 산더미이다. ⛰️