대망의 4주차가 시작됐다. 3주차까지 그동안 프리코스에 나왔던 미션들이 나와서 이번에도 예전 미션이 나올줄 알았는데 아니었다..!!
3시가 되고 메일이 왔고 요구사항을 읽어본 나는 멘붕이 왔다.
그동안의 요구사항보다 2배는 길었고 요구사항을 주는 방식도 달랐다. 회사에서 직접 일을 주는 형식으로 나와서 진짜로 일을 하는 느낌이 들었다.
전체 요구사항을 여기에 올리고 싶지만 너무 길어서 설계 내용을 올려봐야겠다.
적용된 이벤트가 하나도 없는 경우
안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.
12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)
26
주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)
타파스-1,제로콜라-1
12월 26일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!
<주문 메뉴>
타파스 1개
제로콜라 1개
<할인 전 총주문 금액>
8,500원
<증정 메뉴>
없음
<혜택 내역>
없음
<총혜택 금액>
0원
<할인 후 예상 결제 금액>
8,500원
<12월 이벤트 배지>
없음
기대하는 '12월 이벤트 플래너'의 예시 모습
안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.
12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)
3
주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)
티본스테이크-1,바비큐립-1,초코케이크-2,제로콜라-1
12월 3일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!
<주문 메뉴>
티본스테이크 1개
바비큐립 1개
초코케이크 2개
제로콜라 1개
<할인 전 총주문 금액>
142,000원
<증정 메뉴>
샴페인 1개
<혜택 내역>
크리스마스 디데이 할인: -1,200원
평일 할인: -4,046원
특별 할인: -1,000원
증정 이벤트: -25,000원
<총혜택 금액>
-31,246원
<할인 후 예상 결제 금액>
135,754원
<12월 이벤트 배지>
산타
우선 프로그램이 흘러가는 순서는 이렇다.
슬쩍 보기만 해도 필요한 클래스들이 되게 많다.
제일 먼저 생각한 것은 Enum
클래스였다. 처음에는 모든 메뉴들을 한 클래스에 모아서 하려고 했는데 그렇게 하면 애피타이저
, 메인메뉴
, 디저트
, 음료
의 구분이 어려울 것 같았다. 어떻게든 할 수는 있었겠지만 메뉴별로 구분하는 것이 더 편할 것 같아서 그렇게 진행했다.
## 기능
### 메뉴
- [X] 애피타이저 ENUM
- [X] 메인 ENUM
- [X] 디저트 ENUM
- [X] 음료 ENUM
### 날짜
- [X] 요일, 특별날짜, 25일까지의 날짜, 평일, 주말로 구분
- [X] 1일부터 31일까지
- [X] 특별 할인 날짜 저장(3, 10, 17, 24, 25, 31)
### 뱃지
- [X] 5천원 이상 10000원 미만 별
- [X] 10000원 이상 20000원 미만 별
- [X] 20000원 이상 산타
- [X] 그 외 "없음"
-
### 입력
- [X] 방문 날짜 입력
- [X] Integer 확인
- [X] 범위 확인
- [X] 메뉴/개수 입력
- [X] ',' 기준으로 자르기
- [X] '-' 기준으로 자르기
- [X] String과 Integer 확인
### 출력
- [X] 주문 메뉴(Map 사용)
- [X] 할인 전 총금액(int)
- [X] 증정 메뉴(String)
- [X] 혜택 내역(List)
- [X] 적용되는 혜택만 출력
- [X] 총혜택금액(int)
- [X] 할인 후 예상 결제 금액(int)
- [X] 배지(String)
### 이벤트 패키지
#### 총혜택 금액
- [X] 총혜택금액 = 할인 금액 + 증정 메뉴
### 크리스마스 디데이 할인
- [X] 26일 부터는 적용 X
- [X] 시작 금액 : 1000원
- [X] 디데이 할인 = (날짜 - 1) X 100
- [X] '디데이 할인' 만큼 할인 금액 증가
### 평일 할인
- [X] 평일(일~목) 디저트 메뉴 2023원씩 할인
### 주말 할인
- [X] 주말(금~토) 메인 메뉴 2023원씩 할인
### 특별 할인
- [X] 일요일, 25일이면 1000원 할인
### 증정이벤트
- [X] 총주문 금액 생성자로 받아옴
- [X] 총주문금액 12만원 이상이면 증정품(샴페인 25000원) 증정
### 이벤트 배지 선정
- [X] 총혜택금액 생성자로 받아옴
- [X] 5000~10000 : 별, 10000~20000 : 트리, 20000~ : 산타
### 총주문 금액 계산
- [X] 생성자로 메뉴맵 받아옴
- [X] 초기 총 주문 금액 : 0
- [X] 총 주문 금액 += 메뉴가격 * 수량
- [X] 맵 전부 돌면서 계산
- [X] 10000원 이상일 경우 --> 할인 이벤트
### 주문 받기 클래스
- [X] 메뉴의 개수 <= 20
- [X] 음료만 주문 X
- [X] 메뉴의 개수 >= 1
- [X] 메뉴 중복 --> 예외
- [X] 메뉴판에 없는 메뉴 --> 예외
### 재입력
- [X] 잘못된 값이 입력될 경우 IllegalArgumentException
- [X] '[ERROR]' 로 시작하는 메시지 출력
- [X] 오류가 발생한 부분부터 재입력
프로그램은 이런식으로 흘러간다
필요하다고 생각하고 클래스를 나눠봤다. 대충 봐도 클래스가 10개는 넘게 필요한 것을 볼 수 있다. 막막했는데 나는 결국 해냈다 후후
여기에 추가적으로 입력이 잘못들어왔을때 그대로 throw new
로 프로그램을 끝내는 것이 아니고 throw new
로 예외를 일으키되 다시 입력을 받도록 해주는 요구사항이 있었다.
3주차 미션에도 있었는데 제대로 구현을 못해서 4주차 미션때는 고민을 해봤다.
InputView
에서 예외 처리
후 재입력을 받기에는 View
부분에 너무 많은 역할이 생길 것 같아서 이 방법은 포기하고 Model
부분에서 처리하는 방법을 생각했었는데 이렇게 되면 Model
과 View
사이에 의존성이 생기게 되어 결국 MVC 패턴
이 깨지게 된다.
이 방법도 포기하고 고민을 하다가 친구와 얘기를 해봤는데 Controller
부분에서 로직을 만드는 것이 좋을 것 같다는 얘기를 들었다.
다른 미션에서 다른 사람의 코드를 봤을 때 생성자
를 통해 객체를 생성하면서 프로그램을 간결하게 끝냈을 때 코드가 매우 깔끔해 보였던 기억이 있다. 그 이후로 되도록이면 Contoller
부분에는 로직을 안넣으려고 했었는데 재입력
에 대한 부분은 이렇게 하는 것이 맞는 것 같았다.
구현한 방식은 구현 부분에서 설명해야겠다.
언제나 그렇듯 Enum
부터 구현했다. 1차적으로 전부 구현해놓기는 했는데 나중에 바뀔수도 있어서 커밋을 메시지들만 해놨다.
다음은 View
에 있는 클래스들을 정의해줬다
InputView
로 받아올 것은 2개밖에 없었다. 근데 이게 좀 복잡했다..
예약날짜를 받아오는건 예외처리 하나만 해주면 됐는데 문제는 메뉴를 받아오는 메서드였다.
public List<Map<String, Integer>> inputMenu() {
System.out.println(ConsoleMessage.MENU_NAME_QUANTITY.getMessage());
String menu = Console.readLine();
String[] menus = menu.split(",");
List<Map<String, Integer>> menuList = new ArrayList<>();
for (String menuPair : menus) {
String[] menuPairSplit = menuPair.split("-");
Map<String, Integer> menuMap = new HashMap<>();
try {
menuMap.put(menuPairSplit[0], Integer.parseInt(menuPairSplit[1]));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(ErrorMessage.INVALID_ORDER.getMessage());
}catch (ArrayIndexOutOfBoundsException e){
throw new IllegalArgumentException(ErrorMessage.INVALID_ORDER.getMessage());
}
menuList.add(menuMap);
}
return menuList;
}
구현은 이렇게 했다. 우선 입력을 받아서 ,
기준으로 split
해서 배열에 저장하고 거기서 또 -
기준으로 split
해서 List<Map<String, Integer>>에 저장한다. 반환형을 어떻게 할지 고민하다가 Map
으로 메뉴명과 수량을 관리하는 것이 가장 좋을 것 같다고 생각해서 그 것드을 List
에 넣어줬다.
OutputView
는 구현해야 할 메서드가 많았다. 출력해야 할 부분이 많아서 각 부분별로 메서드를 구현해줬다. 받아와서 쪼개고 출력만 하면 돼서 어려운 부분은 딱히 없었다.
View
를 구현했으니 Model
부분을 했다
처음으로 구현한 클래스는 Badge
다. 별다른 이유는 없고 가장 쉬워보였다.
package christmas.enums;
public enum Badges {
STAR("별", 5000),
TREE("트리", 10000),
SANTA("산타", 20000),
NOTHING("없음", 0);
private final String name;
private final int lowerLimitPrice;
Badges(String name, int lowerLimitPrice) {
this.name = name;
this.lowerLimitPrice = lowerLimitPrice;
}
public String getName() {
return name;
}
public int getPrice() {
return lowerLimitPrice;
}
}
Enum
부터 구현한 후 클래스를 구현했다. 로직은 별거 없다.
Date
클래스는 열거형을 어떻게 구성할지 고민을 했었다. WEEKDAY
, WEEKEND
, SPECIAL_DISCOUNT
, NORMAL_DAY
이렇게 나눴는데 이 안에 날짜별로 넣을까 하다가 그 날짜는 클래스에서 구현하기로 헀다. SPECAIL_DISCOUNT
날짜만 넣어줬다.
날짜 할인부분에서 어려움을 겪었었는데 평일할인
과 특별할인
날짜가 겹치면 둘 중 하나만 할인이 됐다. 어떻게 할지 고민했는데 계속 보니까 결국 특별할인
날은 모두 평일할인
이 들어가서 특별할인
날은 두 할인이 모두 되도록 구현해줬다.
어느날인지 정하는 메서드
public Date getDayType() {
if (checkSpecialDiscountDate(date)) {
return SPECIAL_DISCOUNT;
}
if (date % 7 == 1 || date % 7 == 2) {
return WEEKEND;
}
return WEEKDAY;
}
특별할인날인지 확인하는 메서드
private static boolean checkSpecialDiscountDate(int day) {
for (int specialDate : SPECIAL_DISCOUNT_DATES) {
if (day == specialDate) {
return true;
}
}
return false;
}
메뉴별로 Enum
을 만들고 주문받는 클래스를 만들어줬다. 내가 이번 미션에서 가장 어렵다고 생각한 부분이다.
주문을 받아오는것까지는 쉬운데 그 메뉴가 메뉴판에 있는지 확인을 하는 과정을 생각하는 것이 어려웠다. 그리고 메뉴이름과 수량도 검사해야하는데 이 부분도 어려웠다.
다양한 자료구조들을 사용하고 많은 List
들을 만들어서 구현했다. 유효한 메뉴인지 검사하는 부분이 있었는데 코드가 중복되는 부분이 많았다.
이 부분을 묶어서 관리하고 싶었는데 결국 그 방법을 찾지 못했다.
같은 로직이 계속해서 반복되는 모습
private void isValidMenu() {
for (String menuName : menuNameList) {
boolean isValid = false;
isValid = checkAppetizerMenu(menuName, isValid);
isValid = checkMainMenu(menuName, isValid);
isValid = checkDessertMenu(menuName, isValid);
isValid = checkBeverageMenu(menuName, isValid);
if (!isValid) {
throw new IllegalArgumentException(ErrorMessage.INVALID_ORDER.getMessage());
}
}
}
private boolean checkAppetizerMenu(String menuName, boolean isValid) {
for (Appetizer appetizer : Appetizer.values()) {
if (menuName.equals(appetizer.getName())) {
menuTypeList.add(appetizer.getIdentifier());
isValid = true;
break;
}
}
return isValid;
}
private boolean checkMainMenu(String menuName, boolean isValid) {
for (MainMenu mainMenu : MainMenu.values()) {
if (menuName.equals(mainMenu.getName())) {
menuTypeList.add(mainMenu.getIdentifier());
isValid = true;
break;
}
}
return isValid;
}
private boolean checkDessertMenu(String menuName, boolean isValid) {
for (Dessert dessert : Dessert.values()) {
if (menuName.equals(dessert.getName())) {
menuTypeList.add(dessert.getIdentifier());
isValid = true;
break;
}
}
return isValid;
}
private boolean checkBeverageMenu(String menuName, boolean isValid) {
for (Beverage beverage : Beverage.values()) {
if (menuName.equals(beverage.getName())) {
menuTypeList.add(beverage.getIdentifier());
isValid = true;
break;
}
}
return isValid;
}
private void checkOnlyBeverage() {
if (menuTypeList.size() == 1 && menuTypeList.contains(4)) {
throw new IllegalArgumentException(ErrorMessage.INVALID_ORDER.getMessage());
}
}
리팩토링을 해볼때 이 부분에 대해서 생각해봐야할 것 같다.
위에서 말한 재입력에 관련된 부분이다.
private int getDate(){
int date = 0;
try{
date = inputView.inputDate();
DateManager dateManager = new DateManager(date);
}catch (IllegalArgumentException e){
System.out.println(ErrorMessage.OUT_OF_RANGE_DATE.getMessage());
getDate();
}
return date;
}
private List<Map<String, Integer>> getMenu(){
List<Map<String, Integer>> menu = new ArrayList<>();
try{
menu = inputView.inputMenu();
}catch (IllegalArgumentException e){
System.out.println(ErrorMessage.INVALID_ORDER.getMessage());
getMenu();
}
return menu;
}
이 두 부분에서 재입력을 했다. 입력을 받아오고 그 부분에서 IllegalArgumentException
이 발생하면 함수를 다시 실행하도록 재귀적으로 구현해줬다.
이 방법 말고도 다른 방법이 있는지 궁금하다
코드리뷰하면서 한번 봐야겠다!
4주간의 대장정(은 아니고 소장정)이 끝났다. 중간고사 기간이 겹치고 김장도 하고 비콘도 갔다오느라 시간이 정말 부족했다. 그런데도 어떻게든 시간을 내서 모든 미션들을 성공했다는 사실이 기쁘다.
그 동안 이렇게까지 잠이 부족했던적은 없었던 것 같은데 진짜 잠을 하루에 4시간씩밖에 안잔것 같다. 2공에 있는 시간이 집에 있는 시간보다 길 정도....
이번년도 초만해도 자바
의 자
도 모르는 사람이었는데 준호
가 열심히 도와줘서 많이 발전한 것 같다. 앞으로도 열심히 해보자!!
여전히 시간분배를 못하고 있는 것 같다.
미리미리하지 않고 시간이 좀 남은 것 같으면 쉬엄쉬엄하는 경향이 아직도 남아있다. 내가 살면서 1순위로 고쳐야 할 습관이다.
예전보다 나아지기는 했는데 그래도 아직 멀었다. 몰입하자!!!!!!!!!!!
https://github.com/dradnats1012/java-chritmas-6-dradnats1012
모든 테스트코드들과 나머지 코드들은 여기있어요