From 38a3eb9fb09b130887b885851c78208c9b6ccefd Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 14:32:19 +0900 Subject: [PATCH 01/15] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 241 ++++++++---------------------------------------------- 1 file changed, 35 insertions(+), 206 deletions(-) diff --git a/README.md b/README.md index 5c31c2f3d..923ee58aa 100644 --- a/README.md +++ b/README.md @@ -1,210 +1,39 @@ # 미션 - 점심 메뉴 추천 -## 🔍 진행 방식 +## 기능 구현 목록 +1. 시작 메시지 출력 + - “점심 메뉴 추천을 시작합니다.” +2. 코치 이름 입력 + - “코치의 이름을 입력해 주세요. (, 로 구분)” + - 예외 사항 + 1. 형식 오류 시 예외 발생 + - “입력 형식이 올바르지 않습니다.” + 2. 코치가 2명 미만 5명 초과면 예외 발생 + - “코치는 최소 2명 이상 입력해야 합니다.” + - “코치는 최대 5명 이하로 입력해야 합니다.” + 3. 코치의 이름이 2자 미만 4자 초과면 예외 발생 + - “코치의 이름은 2자 이상 4자 이하여야 합니다.” +3. 코치 별 못 먹는 메뉴 입력 + - “%s(이)가 못 먹는 메뉴를 입력해 주세요.” + - 예외 사항 + 1. 형식 오류 시 예외 발생 + - “입력 형식이 올바르지 않습니다.” + 2. 메뉴 개수가 2개 초과인 경우 예외 발생 + - “못 먹는 메뉴의 개수는 2개 이하여야 합니다.” +4. 요일별(월~금 순서대로) 카테고리를 뽑는다. + - 만약 이미 2번 뽑힌 카테고리가 나온다면 다시 뽑는다. + 1. 코치별 메뉴를 뽑는다. + - 만약 한 코치에게 이미 해당 메뉴를 추천했다면 다시 뽑는다. +5. 메뉴 추천 목록 출력 +- ```java + 메뉴 추천 결과입니다. + [ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ] + [ 카테고리 | 한식 | 한식 | 일식 | 중식 | 아시안 ] + [ 토미 | 쌈밥 | 김치찌개 | 미소시루 | 짜장면 | 팟타이 ] + [ 제임스 | 된장찌개 | 비빔밥 | 가츠동 | 토마토 달걀볶음 | 파인애플 볶음밥 ] + [ 포코 | 된장찌개 | 불고기 | 하이라이스 | 탕수육 | 나시고렝 ] + + 추천을 완료했습니다. + ``` -- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. -- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. -- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. -## 📮 미션 제출 방법 - -- 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다. - - GitHub을 활용한 제출 - 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고해 - 제출한다. -- GitHub에 미션을 제출한 후 [우아한테크코스 지원](https://apply.techcourse.co.kr) 사이트에 접속하여 프리코스 과제를 제출한다. - - 자세한 방법은 [제출 가이드](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제출-가이드) - 참고 - - **Pull Request만 보내고 지원 플랫폼에서 과제를 제출하지 않으면 최종 제출하지 않은 것으로 처리되니 주의한다.** - -## 🚨 과제 제출 전 체크 리스트 - 0점 방지 - -- 기능 구현을 모두 정상적으로 했더라도 **요구 사항에 명시된 출력값 형식을 지키지 않을 경우 0점으로 처리**한다. -- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행했을 때 모든 테스트가 성공하는지 확인한다. -- **테스트가 실패할 경우 0점으로 처리**되므로, 반드시 확인 후 제출한다. - -### 테스트 실행 가이드 - -- 터미널에서 `java -version`을 실행하여 Java 버전이 11인지 확인한다. 또는 Eclipse 또는 IntelliJ IDEA와 같은 IDE에서 Java 11로 - 실행되는지 확인한다. -- 터미널에서 Mac 또는 Linux 사용자의 경우 `./gradlew clean test` 명령을 실행하고, - Windows 사용자의 경우 `gradlew.bat clean test` 명령을 실행할 때 모든 테스트가 아래와 같이 통과하는지 확인한다. - -``` -BUILD SUCCESSFUL in 0s -``` - ---- - -## 🚀 기능 요구 사항 - -한 주의 점심 메뉴를 추천해 주는 서비스다. - -- 코치들은 월, 화, 수, 목, 금요일에 점심 식사를 같이 한다. -- 메뉴를 추천하는 과정은 아래와 같이 이뤄진다. - 1. 월요일에 추천할 카테고리를 무작위로 정한다. - 2. 각 코치가 월요일에 먹을 메뉴를 추천한다. - 3. 화, 수, 목, 금요일에 대해 i, ii 과정을 반복한다. -- 코치의 이름은 최소 2글자, 최대 4글자이다. -- 코치는 최소 2명, 최대 5명까지 식사를 함께 한다. -- 각 코치는 최소 0개, 최대 2개의 못 먹는 메뉴가 있다. (`,` 로 구분해서 입력한다.) - - 먹지 못하는 메뉴가 없으면 빈 값을 입력한다. - - 추천을 못하는 경우는 발생하지 않으니 고려하지 않아도 된다. -- 한 주에 같은 카테고리는 최대 2회까지만 고를 수 있다. -- 각 코치에게 한 주에 중복되지 않는 메뉴를 추천해야 한다. - - 예시) - - 구구: 비빔밥, 김치찌개, 쌈밥, 규동, 우동 → 한식을 3회 먹으므로 불가능 - - 토미: 비빔밥, 비빔밥, 규동, 우동, 볶음면 → 한 코치가 같은 메뉴를 먹으므로 불가능 - - 제임스: 비빔밥, 김치찌개, 스시, 가츠동, 짜장면 → 매일 다른 메뉴를 먹으므로 가능 - - 포코: 비빔밥, 김치찌개, 스시, 가츠동, 짜장면 → 제임스와 메뉴가 같지만, 포코는 매번 다른 메뉴를 먹으므로 가능 -- 메뉴 추천을 완료하면 프로그램이 종료된다. -- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 - 받는다. - - `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. - -### 입출력 요구 사항 - -#### 입력 - -- 메뉴 추천을 받을 코치의 이름을 입력받는다. 올바른 값이 아니면 예외 처리한다. - -``` -토미,제임스,포코 -``` - -- 각 코치가 못 먹는 메뉴를 입력받는다. - -``` -우동,스시 -``` - -#### 출력 - -- 서비스 시작 문구 - -``` -점심 메뉴 추천을 시작합니다. -``` - -- 서비스 종료 문구 - -``` -메뉴 추천 결과입니다. -[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ] -[ 카테고리 | 한식 | 한식 | 일식 | 중식 | 아시안 ] -[ 토미 | 쌈밥 | 김치찌개 | 미소시루 | 짜장면 | 팟타이 ] -[ 제임스 | 된장찌개 | 비빔밥 | 가츠동 | 토마토 달걀볶음 | 파인애플 볶음밥 ] -[ 포코 | 된장찌개 | 불고기 | 하이라이스 | 탕수육 | 나시고렝 ] - -추천을 완료했습니다. -``` - -- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다. - -``` -[ERROR] 코치는 최소 2명 이상 입력해야 합니다. -``` - -#### 실행 결과 예시 - -``` -점심 메뉴 추천을 시작합니다. - -코치의 이름을 입력해 주세요. (, 로 구분) -토미,제임스,포코 - -토미(이)가 못 먹는 메뉴를 입력해 주세요. -우동,스시 - -제임스(이)가 못 먹는 메뉴를 입력해 주세요. -뇨끼,월남쌈 - -포코(이)가 못 먹는 메뉴를 입력해 주세요. -마파두부,고추잡채 - -메뉴 추천 결과입니다. -[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ] -[ 카테고리 | 한식 | 한식 | 일식 | 중식 | 아시안 ] -[ 토미 | 쌈밥 | 김치찌개 | 미소시루 | 짜장면 | 팟타이 ] -[ 제임스 | 된장찌개 | 비빔밥 | 가츠동 | 토마토 달걀볶음 | 파인애플 볶음밥 ] -[ 포코 | 된장찌개 | 불고기 | 하이라이스 | 탕수육 | 나시고렝 ] - -추천을 완료했습니다. -``` - ---- - -## 🎯 프로그래밍 요구 사항 - -- JDK 11 버전에서 실행 가능해야 한다. **JDK 11에서 정상적으로 동작하지 않을 경우 0점 처리한다.** -- 프로그램 실행의 시작점은 `Application`의 `main()`이다. -- `build.gradle` 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다. -- [Java 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java) 가이드를 - 준수하며 프로그래밍한다. -- 프로그램 종료 시 `System.exit()`를 호출하지 않는다. -- 프로그램 구현이 완료되면 `ApplicationTest`의 모든 테스트가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.** -- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다. -- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. - - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. -- 3항 연산자를 쓰지 않는다. -- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. - - 함수(또는 메서드)가 한 가지 일만 잘하도록 구현한다. -- else 예약어를 쓰지 않는다. - - 힌트: if 조건절에서 값을 return 하는 방식으로 구현하면 else를 사용하지 않아도 된다. - - else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. - -### 카테고리와 메뉴 요구 사항 - -- 메뉴 추천 서비스에서 추천할 수 있는 카테고리와 각 카테고리의 메뉴는 아래와 같다. - -``` -일식: 규동, 우동, 미소시루, 스시, 가츠동, 오니기리, 하이라이스, 라멘, 오코노미야끼 -한식: 김밥, 김치찌개, 쌈밥, 된장찌개, 비빔밥, 칼국수, 불고기, 떡볶이, 제육볶음 -중식: 깐풍기, 볶음면, 동파육, 짜장면, 짬뽕, 마파두부, 탕수육, 토마토 달걀볶음, 고추잡채 -아시안: 팟타이, 카오 팟, 나시고렝, 파인애플 볶음밥, 쌀국수, 똠얌꿍, 반미, 월남쌈, 분짜 -양식: 라자냐, 그라탱, 뇨끼, 끼슈, 프렌치 토스트, 바게트, 스파게티, 피자, 파니니 -``` - -#### 카테고리 - -- 추천할 - 카테고리는 [`camp.nextstep.edu.missionutils`](https://github.com/woowacourse-projects/mission-utils)에서 - 제공하는 `Randoms.pickNumberInRange()`에서 생성해 준 값을 이용하여 정해야 한다. - -```java -// 예시 코드. 사용하는 자료 구조에 따라 난수를 적절하게 가공해도 된다. -String category = categories.get(Randoms.pickNumberInRange(1, 5)); -``` - -- 임의로 카테고리의 순서 또는 데이터를 변경하면 안 된다. - - `Randoms.pickNumberInRange()`의 결과가 **1이면 일식, 2면 한식, 3이면 중식, 4면 아시안, 5면 양식**을 추천해야 한다. -- 추천할 수 없는 카테고리인 경우 다시 `Randoms.pickNumberInRange()`를 통해 임의의 값을 생성해서 추천할 카테고리를 정해야 한다. - -#### 메뉴 - -- 추천할 메뉴는 정해진 카테고리에 있는 - 메뉴를 [`camp.nextstep.edu.missionutils`](https://github.com/woowacourse-projects/mission-utils)에서 - 제공하는 `Randoms.shuffle()`을 통해 임의의 순서로 섞은 후, 첫 번째 값을 사용해야 한다. - - 카테고리에 포함되는 메뉴 목록을 `List` 형태로 준비한다. - -```java -String menu = Randoms.shuffle(menus).get(0); -``` - -- 임의로 메뉴의 순서 또는 데이터를 변경하면 안 된다. - - `Randoms.shuffle()` 메서드의 인자로 전달되는 메뉴 데이터는, 최초에 제공한 목록을 그대로 전달해야 한다. - - 코치에게 추천할 메뉴를 정할 때 이미 추천한 메뉴, 먹지 못하는 메뉴도 포함된 리스트를 전달해야 한다. -- 추천할 수 없는 메뉴인 경우 다시 섞은 후 첫 번째 값을 사용해야 한다. - ---- - -## ✏️ 과제 진행 요구 사항 - -- 미션은 [java-menu](https://github.com/woowacourse-precourse/java-menu) 저장소를 Fork & Clone해 시작한다. -- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가한다. -- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가한다. - - [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성한다. -- 과제 진행 및 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) - 문서를 참고한다. - - 소감은 간소하게 입력해도 된다. 예를 들어, "."만 입력해도 좋다. From 7533ff439d48be91e8ead33c8213f568ccb13d6b Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 14:34:34 +0900 Subject: [PATCH 02/15] =?UTF-8?q?feat:=20=EC=8B=9C=EC=9E=91=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/menu/Application.java | 4 +++- src/main/java/menu/view/OutputView.java | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/main/java/menu/view/OutputView.java diff --git a/src/main/java/menu/Application.java b/src/main/java/menu/Application.java index 6340b6f33..93e9136c4 100644 --- a/src/main/java/menu/Application.java +++ b/src/main/java/menu/Application.java @@ -1,7 +1,9 @@ package menu; +import menu.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + OutputView.printStart(); } } diff --git a/src/main/java/menu/view/OutputView.java b/src/main/java/menu/view/OutputView.java new file mode 100644 index 000000000..d7b926f83 --- /dev/null +++ b/src/main/java/menu/view/OutputView.java @@ -0,0 +1,15 @@ +package menu.view; + +public class OutputView { + + private static final String NEW_LINE = System.lineSeparator(); + private static final String START_MESSAGE = "점심 메뉴 추천을 시작합니다."; + + public static void printStart() { + System.out.println(START_MESSAGE); + } + + public static void printErrorMessage(IllegalArgumentException e) { + System.out.println(e.getMessage()); + } +} From 1e59826da3c4c15d6430c308d79c1ef2db580c23 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 15:02:51 +0900 Subject: [PATCH 03/15] =?UTF-8?q?feat:=20=EC=BD=94=EC=B9=98=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=9E=85=EB=A0=A5=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/menu/Application.java | 22 +++++++++++++ src/main/java/menu/constant/ErrorMessage.java | 21 +++++++++++++ src/main/java/menu/domain/Coach.java | 31 +++++++++++++++++++ src/main/java/menu/domain/Recommendation.java | 24 ++++++++++++++ src/main/java/menu/util/InputParser.java | 30 ++++++++++++++++++ src/main/java/menu/util/Validator.java | 30 ++++++++++++++++++ src/main/java/menu/view/InputView.java | 13 ++++++++ 7 files changed, 171 insertions(+) create mode 100644 src/main/java/menu/constant/ErrorMessage.java create mode 100644 src/main/java/menu/domain/Coach.java create mode 100644 src/main/java/menu/domain/Recommendation.java create mode 100644 src/main/java/menu/util/InputParser.java create mode 100644 src/main/java/menu/util/Validator.java create mode 100644 src/main/java/menu/view/InputView.java diff --git a/src/main/java/menu/Application.java b/src/main/java/menu/Application.java index 93e9136c4..63bc101e8 100644 --- a/src/main/java/menu/Application.java +++ b/src/main/java/menu/Application.java @@ -1,9 +1,31 @@ package menu; +import java.util.List; +import menu.domain.Recommendation; +import menu.util.InputParser; +import menu.view.InputView; import menu.view.OutputView; public class Application { + + private static Recommendation recommendation; + public static void main(String[] args) { OutputView.printStart(); + while (true) { + try { + String readCoachNames = InputView.readCoachNames(); + List coachNames = InputParser.parseCoachNames(readCoachNames); + + recommendation = Recommendation.newInstance(); + + for (String coachName : coachNames) { + recommendation.addCoach(coachName); + } + break; + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } } } diff --git a/src/main/java/menu/constant/ErrorMessage.java b/src/main/java/menu/constant/ErrorMessage.java new file mode 100644 index 000000000..29a199ac4 --- /dev/null +++ b/src/main/java/menu/constant/ErrorMessage.java @@ -0,0 +1,21 @@ +package menu.constant; + +public enum ErrorMessage { + + FORMAT_ERROR("입력 형식이 올바르지 않습니다."), + COACH_COUNT_MIN_ERROR("코치는 최소 2명 이상 입력해야 합니다."), + COACH_COUNT_MAX_ERROR("코치는 5명 이하로 입력해야 합니다."), + COACH_NAME_LENGTH_ERROR("코치의 이름은 2자 이상 4자 이하여야 합니다."), + ; + + private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; + private final String errorMessage; + + ErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorMessage(Object... args) { + return ERROR_MESSAGE_PREFIX + String.format(errorMessage, args); + } +} diff --git a/src/main/java/menu/domain/Coach.java b/src/main/java/menu/domain/Coach.java new file mode 100644 index 000000000..113b8ac83 --- /dev/null +++ b/src/main/java/menu/domain/Coach.java @@ -0,0 +1,31 @@ +package menu.domain; + +import static menu.constant.ErrorMessage.COACH_NAME_LENGTH_ERROR; + +import java.util.ArrayList; +import java.util.List; + +public class Coach { + + private final String name; + private final List rejectedMenus; + private final List recommendedMenus; + + private Coach(String name) { + this.name = name; + this.rejectedMenus = new ArrayList<>(); + this.recommendedMenus = new ArrayList<>(); + } + + public static Coach from(String name) { + validate(name); + + return new Coach(name); + } + + private static void validate(String name) { + if (name.length() < 2 || name.length() > 4) { + throw new IllegalArgumentException(COACH_NAME_LENGTH_ERROR.getErrorMessage()); + } + } +} diff --git a/src/main/java/menu/domain/Recommendation.java b/src/main/java/menu/domain/Recommendation.java new file mode 100644 index 000000000..14e2395e3 --- /dev/null +++ b/src/main/java/menu/domain/Recommendation.java @@ -0,0 +1,24 @@ +package menu.domain; + +import java.util.ArrayList; +import java.util.List; + +public class Recommendation { + + private final List coaches; + private final List categories; + + private Recommendation() { + this.categories = new ArrayList<>(); + this.coaches = new ArrayList<>(); + } + + public static Recommendation newInstance() { + return new Recommendation(); + } + + public void addCoach(String coachName) { + Coach coach = Coach.from(coachName); + coaches.add(coach); + } +} diff --git a/src/main/java/menu/util/InputParser.java b/src/main/java/menu/util/InputParser.java new file mode 100644 index 000000000..416713568 --- /dev/null +++ b/src/main/java/menu/util/InputParser.java @@ -0,0 +1,30 @@ +package menu.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class InputParser { + + private static final String DELIMITER = ","; + + private InputParser() { + } + + private static List parseToElements(String rawInput) { + Validator.validateCsvFormat(rawInput.strip()); + + return Stream.of(rawInput.split(DELIMITER)) + .map(String::strip) + .collect(Collectors.toList()); + } + + public static List parseCoachNames(String rawInput) { + List names = parseToElements(rawInput); + + Validator.validateCoachNames(names); + + return names; + } +} diff --git a/src/main/java/menu/util/Validator.java b/src/main/java/menu/util/Validator.java new file mode 100644 index 000000000..45ebda273 --- /dev/null +++ b/src/main/java/menu/util/Validator.java @@ -0,0 +1,30 @@ +package menu.util; + +import static menu.constant.ErrorMessage.COACH_COUNT_MAX_ERROR; +import static menu.constant.ErrorMessage.COACH_COUNT_MIN_ERROR; +import static menu.constant.ErrorMessage.FORMAT_ERROR; + +import java.util.List; + +public final class Validator { + + private static final String CSV_FORMAT = "^ *[가-힣a-zA-Z]+ *(, *[가-힣a-zA-Z]+ *)*$"; + + private Validator() {} + + public static void validateCsvFormat(String input) { + if (!input.matches(CSV_FORMAT)) { + throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); + } + } + + public static void validateCoachNames(List names) { + if (names.size() < 2) { + throw new IllegalArgumentException(COACH_COUNT_MIN_ERROR.getErrorMessage()); + } + + if (names.size() > 5) { + throw new IllegalArgumentException(COACH_COUNT_MAX_ERROR.getErrorMessage()); + } + } +} diff --git a/src/main/java/menu/view/InputView.java b/src/main/java/menu/view/InputView.java new file mode 100644 index 000000000..88969631c --- /dev/null +++ b/src/main/java/menu/view/InputView.java @@ -0,0 +1,13 @@ +package menu.view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + + private static final String COACH_NAME_REQUEST = "코치의 이름을 입력해 주세요. (, 로 구분)"; + + public static String readCoachNames() { + System.out.println(COACH_NAME_REQUEST); + return Console.readLine(); + } +} From 3cf3a7997ee8b1b325a8331379c092da2dcdafaa Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 15:14:06 +0900 Subject: [PATCH 04/15] =?UTF-8?q?refactor:=20=EC=BD=94=EC=B9=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=9E=85=EB=A0=A5=20=EB=B0=8F=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/menu/Application.java | 25 +++++++++++++++-- src/main/java/menu/domain/Coach.java | 12 +++++++++ src/main/java/menu/domain/Coaches.java | 27 +++++++++++++++++++ src/main/java/menu/domain/Recommendation.java | 5 ---- src/main/java/menu/dto/CoachesDto.java | 18 +++++++++++++ 5 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 src/main/java/menu/domain/Coaches.java create mode 100644 src/main/java/menu/dto/CoachesDto.java diff --git a/src/main/java/menu/Application.java b/src/main/java/menu/Application.java index 63bc101e8..98670b8f6 100644 --- a/src/main/java/menu/Application.java +++ b/src/main/java/menu/Application.java @@ -1,7 +1,10 @@ package menu; import java.util.List; +import menu.domain.Coach; +import menu.domain.Coaches; import menu.domain.Recommendation; +import menu.dto.CoachesDto; import menu.util.InputParser; import menu.view.InputView; import menu.view.OutputView; @@ -9,6 +12,8 @@ public class Application { private static Recommendation recommendation; + private static Coaches coaches; + private static CoachesDto coachesDto; public static void main(String[] args) { OutputView.printStart(); @@ -17,15 +22,31 @@ public static void main(String[] args) { String readCoachNames = InputView.readCoachNames(); List coachNames = InputParser.parseCoachNames(readCoachNames); - recommendation = Recommendation.newInstance(); + coaches = Coaches.newInstance(); for (String coachName : coachNames) { - recommendation.addCoach(coachName); + coaches.addCoach(coachName); } + + coachesDto = coaches.getCoachesDto(); + break; } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e); } } + + List coaches = coachesDto.getCoaches(); + for (Coach coach : coaches) { + while (true) { + try { + String readRejectedMenus = InputView.readRejectedMenus(coach); + + break; + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } + } } } diff --git a/src/main/java/menu/domain/Coach.java b/src/main/java/menu/domain/Coach.java index 113b8ac83..abc28573d 100644 --- a/src/main/java/menu/domain/Coach.java +++ b/src/main/java/menu/domain/Coach.java @@ -28,4 +28,16 @@ private static void validate(String name) { throw new IllegalArgumentException(COACH_NAME_LENGTH_ERROR.getErrorMessage()); } } + + public String getName() { + return name; + } + + public List getRecommendedMenus() { + return recommendedMenus; + } + + public List getRejectedMenus() { + return rejectedMenus; + } } diff --git a/src/main/java/menu/domain/Coaches.java b/src/main/java/menu/domain/Coaches.java new file mode 100644 index 000000000..0788f2804 --- /dev/null +++ b/src/main/java/menu/domain/Coaches.java @@ -0,0 +1,27 @@ +package menu.domain; + +import java.util.ArrayList; +import java.util.List; +import menu.dto.CoachesDto; + +public class Coaches { + + private final List coaches; + + public Coaches() { + this.coaches = new ArrayList<>(); + } + + public static Coaches newInstance() { + return new Coaches(); + } + + public void addCoach(String coachName) { + Coach coach = Coach.from(coachName); + coaches.add(coach); + } + + public CoachesDto getCoachesDto() { + return new CoachesDto(coaches); + } +} diff --git a/src/main/java/menu/domain/Recommendation.java b/src/main/java/menu/domain/Recommendation.java index 14e2395e3..5d952d70a 100644 --- a/src/main/java/menu/domain/Recommendation.java +++ b/src/main/java/menu/domain/Recommendation.java @@ -16,9 +16,4 @@ private Recommendation() { public static Recommendation newInstance() { return new Recommendation(); } - - public void addCoach(String coachName) { - Coach coach = Coach.from(coachName); - coaches.add(coach); - } } diff --git a/src/main/java/menu/dto/CoachesDto.java b/src/main/java/menu/dto/CoachesDto.java new file mode 100644 index 000000000..b4a7905cf --- /dev/null +++ b/src/main/java/menu/dto/CoachesDto.java @@ -0,0 +1,18 @@ +package menu.dto; + +import java.util.ArrayList; +import java.util.List; +import menu.domain.Coach; + +public class CoachesDto { + + private final List coaches; + + public CoachesDto(List coaches) { + this.coaches = new ArrayList<>(coaches); + } + + public List getCoaches() { + return coaches; + } +} From 0a4680a27c3b7d3258a251a374fb89dbc1ad239c Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 15:35:44 +0900 Subject: [PATCH 05/15] =?UTF-8?q?feat:=20=EC=BD=94=EC=B9=98=20=EB=B3=84=20?= =?UTF-8?q?=EB=AA=BB=20=EB=A8=B9=EB=8A=94=20=EB=A9=94=EB=89=B4=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/menu/Application.java | 4 +++- src/main/java/menu/constant/Constant.java | 16 +++++++++++++ src/main/java/menu/constant/ErrorMessage.java | 2 ++ src/main/java/menu/domain/Coach.java | 23 +++++++++++++++++++ src/main/java/menu/util/InputParser.java | 15 ++++++++++-- src/main/java/menu/util/Validator.java | 19 ++++++++++++++- src/main/java/menu/view/InputView.java | 7 ++++++ 7 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 src/main/java/menu/constant/Constant.java diff --git a/src/main/java/menu/Application.java b/src/main/java/menu/Application.java index 98670b8f6..2c4dd47a8 100644 --- a/src/main/java/menu/Application.java +++ b/src/main/java/menu/Application.java @@ -37,11 +37,13 @@ public static void main(String[] args) { } List coaches = coachesDto.getCoaches(); + for (Coach coach : coaches) { while (true) { try { String readRejectedMenus = InputView.readRejectedMenus(coach); - + List rejectedMenus = InputParser.parseRejectedMenus(readRejectedMenus); + coach.addRejectedMenus(rejectedMenus); break; } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e); diff --git a/src/main/java/menu/constant/Constant.java b/src/main/java/menu/constant/Constant.java new file mode 100644 index 000000000..fceaec3ee --- /dev/null +++ b/src/main/java/menu/constant/Constant.java @@ -0,0 +1,16 @@ +package menu.constant; + +import java.util.List; +import java.util.Map; + +public final class Constant { + + public static final List CATEGORY_NAMES = List.of("일식", "한식", "중식", "아시안", "양식"); + public static final Map> CATEGORIES = Map.of( + "일식", List.of("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼"), + "한식", List.of("김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음"), + "중식", List.of("깐풍기", "볶음면", "동파육", "짜장면", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채"), + "아시안", List.of("팟타이", "카오 팟", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜"), + "양식", List.of("라자냐", "그라탱", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "스파게티", "피자", "파니니") + ); +} diff --git a/src/main/java/menu/constant/ErrorMessage.java b/src/main/java/menu/constant/ErrorMessage.java index 29a199ac4..2a8372780 100644 --- a/src/main/java/menu/constant/ErrorMessage.java +++ b/src/main/java/menu/constant/ErrorMessage.java @@ -6,6 +6,8 @@ public enum ErrorMessage { COACH_COUNT_MIN_ERROR("코치는 최소 2명 이상 입력해야 합니다."), COACH_COUNT_MAX_ERROR("코치는 5명 이하로 입력해야 합니다."), COACH_NAME_LENGTH_ERROR("코치의 이름은 2자 이상 4자 이하여야 합니다."), + REJECTED_MENU_COUNT_ERROR("못 먹는 메뉴의 개수는 2개 이하여야 합니다"), + NO_EXIST_MENU("없는 메뉴입니다.") ; private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; diff --git a/src/main/java/menu/domain/Coach.java b/src/main/java/menu/domain/Coach.java index abc28573d..e7edb7807 100644 --- a/src/main/java/menu/domain/Coach.java +++ b/src/main/java/menu/domain/Coach.java @@ -1,6 +1,8 @@ package menu.domain; +import static menu.constant.Constant.CATEGORIES; import static menu.constant.ErrorMessage.COACH_NAME_LENGTH_ERROR; +import static menu.constant.ErrorMessage.NO_EXIST_MENU; import java.util.ArrayList; import java.util.List; @@ -40,4 +42,25 @@ public List getRecommendedMenus() { public List getRejectedMenus() { return rejectedMenus; } + + public void addRejectedMenus(List rejectedMenus) { + validateExistence(rejectedMenus); + } + + private void validateExistence(List rejectedMenus) { + int check = 0; + for (String rejectedMenu : rejectedMenus) { + for (String categoryName : CATEGORIES.keySet()) { + List menus = CATEGORIES.get(categoryName); + if (menus.contains(rejectedMenu)) { + check = 1; + break; + } + } + + if (check == 0) { + throw new IllegalArgumentException(NO_EXIST_MENU.getErrorMessage()); + } + } + } } diff --git a/src/main/java/menu/util/InputParser.java b/src/main/java/menu/util/InputParser.java index 416713568..85e1390c8 100644 --- a/src/main/java/menu/util/InputParser.java +++ b/src/main/java/menu/util/InputParser.java @@ -1,6 +1,5 @@ package menu.util; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -13,7 +12,7 @@ private InputParser() { } private static List parseToElements(String rawInput) { - Validator.validateCsvFormat(rawInput.strip()); + rawInput = rawInput.strip(); return Stream.of(rawInput.split(DELIMITER)) .map(String::strip) @@ -21,10 +20,22 @@ private static List parseToElements(String rawInput) { } public static List parseCoachNames(String rawInput) { + Validator.validateCoachNameFormat(rawInput.strip()); + List names = parseToElements(rawInput); Validator.validateCoachNames(names); return names; } + + public static List parseRejectedMenus(String rawInput) { + Validator.validateRejectedMenuFormat(rawInput.strip()); + + List menus = parseToElements(rawInput); + + Validator.validateRejectedMenus(menus); + + return menus; + } } diff --git a/src/main/java/menu/util/Validator.java b/src/main/java/menu/util/Validator.java index 45ebda273..ba22e94e2 100644 --- a/src/main/java/menu/util/Validator.java +++ b/src/main/java/menu/util/Validator.java @@ -3,6 +3,7 @@ import static menu.constant.ErrorMessage.COACH_COUNT_MAX_ERROR; import static menu.constant.ErrorMessage.COACH_COUNT_MIN_ERROR; import static menu.constant.ErrorMessage.FORMAT_ERROR; +import static menu.constant.ErrorMessage.REJECTED_MENU_COUNT_ERROR; import java.util.List; @@ -12,7 +13,7 @@ public final class Validator { private Validator() {} - public static void validateCsvFormat(String input) { + public static void validateCoachNameFormat(String input) { if (!input.matches(CSV_FORMAT)) { throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); } @@ -27,4 +28,20 @@ public static void validateCoachNames(List names) { throw new IllegalArgumentException(COACH_COUNT_MAX_ERROR.getErrorMessage()); } } + + public static void validateRejectedMenuFormat(String input) { + if (input.isBlank()) { + return; + } + + if (!input.matches(CSV_FORMAT)) { + throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); + } + } + + public static void validateRejectedMenus(List menus) { + if (menus.size() > 2) { + throw new IllegalArgumentException(REJECTED_MENU_COUNT_ERROR.getErrorMessage()); + } + } } diff --git a/src/main/java/menu/view/InputView.java b/src/main/java/menu/view/InputView.java index 88969631c..7a7739d18 100644 --- a/src/main/java/menu/view/InputView.java +++ b/src/main/java/menu/view/InputView.java @@ -1,13 +1,20 @@ package menu.view; import camp.nextstep.edu.missionutils.Console; +import menu.domain.Coach; public class InputView { private static final String COACH_NAME_REQUEST = "코치의 이름을 입력해 주세요. (, 로 구분)"; + private static final String REJECTED_MENUS_REQUEST = "%s(이)가 못 먹는 메뉴를 입력해 주세요.\n"; public static String readCoachNames() { System.out.println(COACH_NAME_REQUEST); return Console.readLine(); } + + public static String readRejectedMenus(Coach coach) { + System.out.printf(REJECTED_MENUS_REQUEST, coach.getName()); + return Console.readLine(); + } } From 974228b8f8f93aad4bc6526d319acc60853f552d Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 15:38:42 +0900 Subject: [PATCH 06/15] =?UTF-8?q?feat:=20=EC=BD=94=EC=B9=98=20=EB=B3=84=20?= =?UTF-8?q?=EB=AA=BB=20=EB=A8=B9=EB=8A=94=20=EB=A9=94=EB=89=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/menu/Application.java | 3 +++ src/main/java/menu/domain/Coach.java | 28 ++++++++++++---------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/main/java/menu/Application.java b/src/main/java/menu/Application.java index 2c4dd47a8..4086bfc2c 100644 --- a/src/main/java/menu/Application.java +++ b/src/main/java/menu/Application.java @@ -44,6 +44,9 @@ public static void main(String[] args) { String readRejectedMenus = InputView.readRejectedMenus(coach); List rejectedMenus = InputParser.parseRejectedMenus(readRejectedMenus); coach.addRejectedMenus(rejectedMenus); + + + break; } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e); diff --git a/src/main/java/menu/domain/Coach.java b/src/main/java/menu/domain/Coach.java index e7edb7807..83bae86fc 100644 --- a/src/main/java/menu/domain/Coach.java +++ b/src/main/java/menu/domain/Coach.java @@ -10,12 +10,11 @@ public class Coach { private final String name; - private final List rejectedMenus; + private List rejectedMenus; private final List recommendedMenus; private Coach(String name) { this.name = name; - this.rejectedMenus = new ArrayList<>(); this.recommendedMenus = new ArrayList<>(); } @@ -44,23 +43,20 @@ public List getRejectedMenus() { } public void addRejectedMenus(List rejectedMenus) { - validateExistence(rejectedMenus); - } - - private void validateExistence(List rejectedMenus) { - int check = 0; for (String rejectedMenu : rejectedMenus) { - for (String categoryName : CATEGORIES.keySet()) { - List menus = CATEGORIES.get(categoryName); - if (menus.contains(rejectedMenu)) { - check = 1; - break; - } - } + validateExistence(rejectedMenu); + } + this.rejectedMenus = new ArrayList<>(rejectedMenus); + } - if (check == 0) { - throw new IllegalArgumentException(NO_EXIST_MENU.getErrorMessage()); + private void validateExistence(String rejectedMenu) { + for (String categoryName : CATEGORIES.keySet()) { + List menus = CATEGORIES.get(categoryName); + if (menus.contains(rejectedMenu)) { + return; } } + + throw new IllegalArgumentException(NO_EXIST_MENU.getErrorMessage()); } } From 858baad2bd05edf5d60a642aa828c813b456fb19 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 16:34:04 +0900 Subject: [PATCH 07/15] =?UTF-8?q?feat:=20=EC=9A=94=EC=9D=BC=EB=B3=84(?= =?UTF-8?q?=EC=9B=94~=EA=B8=88=20=EC=88=9C=EC=84=9C=EB=8C=80=EB=A1=9C)=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=B0=8F=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=20=EC=B6=94=EC=B2=9C=20=EA=B8=B0=EB=8A=A5=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/menu/Application.java | 60 ++++++++++++++++--- src/main/java/menu/domain/Coach.java | 8 +++ src/main/java/menu/domain/Coaches.java | 23 ++++++- src/main/java/menu/domain/Recommendation.java | 31 ++++++++-- src/main/java/menu/dto/CoachNamesDto.java | 17 ++++++ src/main/java/menu/dto/CoachesDto.java | 18 ------ .../generator/RandomCategoryGenerator.java | 13 ++++ .../menu/generator/RandomMenuGenerator.java | 11 ++++ src/main/java/menu/view/InputView.java | 4 +- src/main/java/menu/view/OutputView.java | 3 +- 10 files changed, 150 insertions(+), 38 deletions(-) create mode 100644 src/main/java/menu/dto/CoachNamesDto.java delete mode 100644 src/main/java/menu/dto/CoachesDto.java create mode 100644 src/main/java/menu/generator/RandomCategoryGenerator.java create mode 100644 src/main/java/menu/generator/RandomMenuGenerator.java diff --git a/src/main/java/menu/Application.java b/src/main/java/menu/Application.java index 4086bfc2c..a6c55a8ea 100644 --- a/src/main/java/menu/Application.java +++ b/src/main/java/menu/Application.java @@ -1,10 +1,14 @@ package menu; +import static menu.constant.Constant.CATEGORIES; + import java.util.List; import menu.domain.Coach; import menu.domain.Coaches; import menu.domain.Recommendation; -import menu.dto.CoachesDto; +import menu.dto.CoachNamesDto; +import menu.generator.RandomCategoryGenerator; +import menu.generator.RandomMenuGenerator; import menu.util.InputParser; import menu.view.InputView; import menu.view.OutputView; @@ -13,7 +17,7 @@ public class Application { private static Recommendation recommendation; private static Coaches coaches; - private static CoachesDto coachesDto; + private static CoachNamesDto coachNamesDto; public static void main(String[] args) { OutputView.printStart(); @@ -28,7 +32,7 @@ public static void main(String[] args) { coaches.addCoach(coachName); } - coachesDto = coaches.getCoachesDto(); + coachNamesDto = coaches.getCoachNamesDto(); break; } catch (IllegalArgumentException e) { @@ -36,16 +40,16 @@ public static void main(String[] args) { } } - List coaches = coachesDto.getCoaches(); + List coachNames = coachNamesDto.getCoachNames(); - for (Coach coach : coaches) { + for (String coachName : coachNames) { while (true) { try { - String readRejectedMenus = InputView.readRejectedMenus(coach); + String readRejectedMenus = InputView.readRejectedMenus(coachName); List rejectedMenus = InputParser.parseRejectedMenus(readRejectedMenus); - coach.addRejectedMenus(rejectedMenus); - + Coach coach = coaches.getCoach(coachName); + coach.addRejectedMenus(rejectedMenus); break; } catch (IllegalArgumentException e) { @@ -53,5 +57,45 @@ public static void main(String[] args) { } } } + + recommendation = Recommendation.from(coaches); + + while (!recommendation.isDone()) { // 추천 끝났으면(추천 카테고리 항목이 5개이면) + String category = RandomCategoryGenerator.generateCategory(); + if (!recommendation.possible(category)) { // 이미 2번 추천 되었으면 + continue; + } + + recommendation.addCategory(category); // 추천 카테고리 추가 + + for (Coach coach : coaches.getCoaches()) { // 코치별로 메뉴 추천 + + while (true) { + List candidateMenus = CATEGORIES.get(category); // 카테고리에 맞는 메뉴 후보 가져오기 + + String menu = RandomMenuGenerator.generateMenu(candidateMenus); + + if (!coach.possible(menu)) { // 이미 추천했거나 못먹는 음식이라면 + continue; + } + + coach.addRecommendedMenu(menu); // 메뉴 추가 + + break; + } + } + } + + System.out.println("메뉴 추천 결과입니다.\n[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ]"); + + List categories = recommendation.getCategories(); + System.out.println("[ 카테고리 | " + String.join(" | ", categories.toArray(new String[0])) + " ]"); + + List coaches1 = recommendation.getCoaches(); + for (Coach coach : coaches1) { + System.out.printf("[ %s | " + String.join(" | ", coach.getRecommendedMenus().toArray(new String[0])) + " ]\n", coach.getName()); + } + + System.out.println("추천을 완료했습니다."); } } diff --git a/src/main/java/menu/domain/Coach.java b/src/main/java/menu/domain/Coach.java index 83bae86fc..2f72d58b2 100644 --- a/src/main/java/menu/domain/Coach.java +++ b/src/main/java/menu/domain/Coach.java @@ -59,4 +59,12 @@ private void validateExistence(String rejectedMenu) { throw new IllegalArgumentException(NO_EXIST_MENU.getErrorMessage()); } + + public boolean possible(String menu) { + return !recommendedMenus.contains(menu) || !rejectedMenus.contains(menu); + } + + public void addRecommendedMenu(String menu) { + this.recommendedMenus.add(menu); + } } diff --git a/src/main/java/menu/domain/Coaches.java b/src/main/java/menu/domain/Coaches.java index 0788f2804..52b7b7253 100644 --- a/src/main/java/menu/domain/Coaches.java +++ b/src/main/java/menu/domain/Coaches.java @@ -2,7 +2,8 @@ import java.util.ArrayList; import java.util.List; -import menu.dto.CoachesDto; +import java.util.stream.Collectors; +import menu.dto.CoachNamesDto; public class Coaches { @@ -21,7 +22,23 @@ public void addCoach(String coachName) { coaches.add(coach); } - public CoachesDto getCoachesDto() { - return new CoachesDto(coaches); + public CoachNamesDto getCoachNamesDto() { + List coachNames = coaches.stream() + .map(Coach::getName) + .collect(Collectors.toList()); + return new CoachNamesDto(coachNames); + } + + public Coach getCoach(String coachName) { + for (Coach coach : coaches) { + if (coach.getName().equals(coachName)) { + return coach; + } + } + return null; + } + + public List getCoaches() { + return coaches; } } diff --git a/src/main/java/menu/domain/Recommendation.java b/src/main/java/menu/domain/Recommendation.java index 5d952d70a..c196a3a8a 100644 --- a/src/main/java/menu/domain/Recommendation.java +++ b/src/main/java/menu/domain/Recommendation.java @@ -1,19 +1,40 @@ package menu.domain; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class Recommendation { - private final List coaches; + private final Coaches coaches; private final List categories; - private Recommendation() { + private Recommendation(Coaches coaches) { this.categories = new ArrayList<>(); - this.coaches = new ArrayList<>(); + this.coaches = coaches; } - public static Recommendation newInstance() { - return new Recommendation(); + public static Recommendation from(Coaches coaches) { + return new Recommendation(coaches); + } + + public boolean isDone() { + return categories.size() == 5; + } + + public boolean possible(String category) { + return Collections.frequency(categories, category) != 2; + } + + public void addCategory(String category) { + categories.add(category); + } + + public List getCategories() { + return categories; + } + + public List getCoaches() { + return coaches.getCoaches(); } } diff --git a/src/main/java/menu/dto/CoachNamesDto.java b/src/main/java/menu/dto/CoachNamesDto.java new file mode 100644 index 000000000..4fe30abcc --- /dev/null +++ b/src/main/java/menu/dto/CoachNamesDto.java @@ -0,0 +1,17 @@ +package menu.dto; + +import java.util.ArrayList; +import java.util.List; + +public class CoachNamesDto { + + private final List coachNames; + + public CoachNamesDto(List coachNames) { + this.coachNames = new ArrayList<>(coachNames); + } + + public List getCoachNames() { + return coachNames; + } +} diff --git a/src/main/java/menu/dto/CoachesDto.java b/src/main/java/menu/dto/CoachesDto.java deleted file mode 100644 index b4a7905cf..000000000 --- a/src/main/java/menu/dto/CoachesDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package menu.dto; - -import java.util.ArrayList; -import java.util.List; -import menu.domain.Coach; - -public class CoachesDto { - - private final List coaches; - - public CoachesDto(List coaches) { - this.coaches = new ArrayList<>(coaches); - } - - public List getCoaches() { - return coaches; - } -} diff --git a/src/main/java/menu/generator/RandomCategoryGenerator.java b/src/main/java/menu/generator/RandomCategoryGenerator.java new file mode 100644 index 000000000..1c10be58a --- /dev/null +++ b/src/main/java/menu/generator/RandomCategoryGenerator.java @@ -0,0 +1,13 @@ +package menu.generator; + +import static menu.constant.Constant.CATEGORY_NAMES; + +import camp.nextstep.edu.missionutils.Randoms; + +public final class RandomCategoryGenerator { + + public static String generateCategory() { + int number = Randoms.pickNumberInRange(1, 5); + return CATEGORY_NAMES.get(number - 1); + } +} diff --git a/src/main/java/menu/generator/RandomMenuGenerator.java b/src/main/java/menu/generator/RandomMenuGenerator.java new file mode 100644 index 000000000..b3dd7e324 --- /dev/null +++ b/src/main/java/menu/generator/RandomMenuGenerator.java @@ -0,0 +1,11 @@ +package menu.generator; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.List; + +public final class RandomMenuGenerator { + + public static String generateMenu(List menus) { + return Randoms.shuffle(menus).get(0); + } +} diff --git a/src/main/java/menu/view/InputView.java b/src/main/java/menu/view/InputView.java index 7a7739d18..d662e9457 100644 --- a/src/main/java/menu/view/InputView.java +++ b/src/main/java/menu/view/InputView.java @@ -13,8 +13,8 @@ public static String readCoachNames() { return Console.readLine(); } - public static String readRejectedMenus(Coach coach) { - System.out.printf(REJECTED_MENUS_REQUEST, coach.getName()); + public static String readRejectedMenus(String coachName) { + System.out.printf(REJECTED_MENUS_REQUEST, coachName); return Console.readLine(); } } diff --git a/src/main/java/menu/view/OutputView.java b/src/main/java/menu/view/OutputView.java index d7b926f83..b3e7f8798 100644 --- a/src/main/java/menu/view/OutputView.java +++ b/src/main/java/menu/view/OutputView.java @@ -2,8 +2,7 @@ public class OutputView { - private static final String NEW_LINE = System.lineSeparator(); - private static final String START_MESSAGE = "점심 메뉴 추천을 시작합니다."; + private static final String START_MESSAGE = "점심 메뉴 추천을 시작합니다.\n"; public static void printStart() { System.out.println(START_MESSAGE); From 7749975cb11b329e42ead99e13800af76b054016 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 17:17:33 +0900 Subject: [PATCH 08/15] =?UTF-8?q?refactor:=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EB=B0=8F=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85=20=EB=B0=8F=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/menu/Application.java | 98 +------------------ src/main/java/menu/constant/Constant.java | 10 ++ .../java/menu/controller/MenuController.java | 60 ++++++++++++ src/main/java/menu/domain/Coach.java | 10 +- src/main/java/menu/domain/Recommendation.java | 12 +-- src/main/java/menu/service/MenuService.java | 70 +++++++++++++ .../java/menu/service/RecommendationDto.java | 23 +++++ src/main/java/menu/util/Validator.java | 7 +- src/main/java/menu/view/OutputView.java | 23 +++++ 9 files changed, 204 insertions(+), 109 deletions(-) create mode 100644 src/main/java/menu/controller/MenuController.java create mode 100644 src/main/java/menu/service/MenuService.java create mode 100644 src/main/java/menu/service/RecommendationDto.java diff --git a/src/main/java/menu/Application.java b/src/main/java/menu/Application.java index a6c55a8ea..60efbbef7 100644 --- a/src/main/java/menu/Application.java +++ b/src/main/java/menu/Application.java @@ -1,101 +1,13 @@ package menu; -import static menu.constant.Constant.CATEGORIES; - -import java.util.List; -import menu.domain.Coach; -import menu.domain.Coaches; -import menu.domain.Recommendation; -import menu.dto.CoachNamesDto; -import menu.generator.RandomCategoryGenerator; -import menu.generator.RandomMenuGenerator; -import menu.util.InputParser; -import menu.view.InputView; -import menu.view.OutputView; +import menu.controller.MenuController; +import menu.service.MenuService; public class Application { - private static Recommendation recommendation; - private static Coaches coaches; - private static CoachNamesDto coachNamesDto; - public static void main(String[] args) { - OutputView.printStart(); - while (true) { - try { - String readCoachNames = InputView.readCoachNames(); - List coachNames = InputParser.parseCoachNames(readCoachNames); - - coaches = Coaches.newInstance(); - - for (String coachName : coachNames) { - coaches.addCoach(coachName); - } - - coachNamesDto = coaches.getCoachNamesDto(); - - break; - } catch (IllegalArgumentException e) { - OutputView.printErrorMessage(e); - } - } - - List coachNames = coachNamesDto.getCoachNames(); - - for (String coachName : coachNames) { - while (true) { - try { - String readRejectedMenus = InputView.readRejectedMenus(coachName); - List rejectedMenus = InputParser.parseRejectedMenus(readRejectedMenus); - - Coach coach = coaches.getCoach(coachName); - coach.addRejectedMenus(rejectedMenus); - - break; - } catch (IllegalArgumentException e) { - OutputView.printErrorMessage(e); - } - } - } - - recommendation = Recommendation.from(coaches); - - while (!recommendation.isDone()) { // 추천 끝났으면(추천 카테고리 항목이 5개이면) - String category = RandomCategoryGenerator.generateCategory(); - if (!recommendation.possible(category)) { // 이미 2번 추천 되었으면 - continue; - } - - recommendation.addCategory(category); // 추천 카테고리 추가 - - for (Coach coach : coaches.getCoaches()) { // 코치별로 메뉴 추천 - - while (true) { - List candidateMenus = CATEGORIES.get(category); // 카테고리에 맞는 메뉴 후보 가져오기 - - String menu = RandomMenuGenerator.generateMenu(candidateMenus); - - if (!coach.possible(menu)) { // 이미 추천했거나 못먹는 음식이라면 - continue; - } - - coach.addRecommendedMenu(menu); // 메뉴 추가 - - break; - } - } - } - - System.out.println("메뉴 추천 결과입니다.\n[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ]"); - - List categories = recommendation.getCategories(); - System.out.println("[ 카테고리 | " + String.join(" | ", categories.toArray(new String[0])) + " ]"); - - List coaches1 = recommendation.getCoaches(); - for (Coach coach : coaches1) { - System.out.printf("[ %s | " + String.join(" | ", coach.getRecommendedMenus().toArray(new String[0])) + " ]\n", coach.getName()); - } - - System.out.println("추천을 완료했습니다."); + MenuService menuService = new MenuService(); + MenuController menuController = new MenuController(menuService); + menuController.run(); } } diff --git a/src/main/java/menu/constant/Constant.java b/src/main/java/menu/constant/Constant.java index fceaec3ee..f20f8289e 100644 --- a/src/main/java/menu/constant/Constant.java +++ b/src/main/java/menu/constant/Constant.java @@ -13,4 +13,14 @@ public final class Constant { "아시안", List.of("팟타이", "카오 팟", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜"), "양식", List.of("라자냐", "그라탱", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "스파게티", "피자", "파니니") ); + + public static final int COACH_NAME_LENGTH_MIN = 2; + public static final int COACH_NAME_LENGTH_MAX = 4; + + public static final int RECOMMENDED_MENUS_SIZE = 5; + + public static final int COACH_COUNT_MIN = 2; + public static final int COACH_COUNT_MAX = 5; + + public static final int REJECTED_MENU_COUNT_MAX = 2; } diff --git a/src/main/java/menu/controller/MenuController.java b/src/main/java/menu/controller/MenuController.java new file mode 100644 index 000000000..1c47513b9 --- /dev/null +++ b/src/main/java/menu/controller/MenuController.java @@ -0,0 +1,60 @@ +package menu.controller; + +import java.util.List; +import menu.dto.CoachNamesDto; +import menu.service.MenuService; +import menu.service.RecommendationDto; +import menu.util.InputParser; +import menu.view.InputView; +import menu.view.OutputView; + +public class MenuController { + + private final MenuService menuService; + + public MenuController(MenuService menuService) { + this.menuService = menuService; + } + + public void run() { + OutputView.printStart(); + + CoachNamesDto coachNamesDto = registerCoachNames(); + List coachNames = coachNamesDto.getCoachNames(); + + for (String coachName : coachNames) { + registerRejectedMenus(coachName); + } + + RecommendationDto result = menuService.getResult(); + OutputView.printResult(result); + } + + private void registerRejectedMenus(String coachName) { + while (true) { + try { + String readRejectedMenus = InputView.readRejectedMenus(coachName); + List rejectedMenus = InputParser.parseRejectedMenus(readRejectedMenus); + + menuService.registerRejectedMenus(coachName, rejectedMenus); + + return; + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } + } + + private CoachNamesDto registerCoachNames() { + while (true) { + try { + String readCoachNames = InputView.readCoachNames(); + List coachNames = InputParser.parseCoachNames(readCoachNames); + + return menuService.registerCoachNames(coachNames); + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } + } +} diff --git a/src/main/java/menu/domain/Coach.java b/src/main/java/menu/domain/Coach.java index 2f72d58b2..4208c7c4a 100644 --- a/src/main/java/menu/domain/Coach.java +++ b/src/main/java/menu/domain/Coach.java @@ -1,11 +1,13 @@ package menu.domain; import static menu.constant.Constant.CATEGORIES; +import static menu.constant.Constant.COACH_NAME_LENGTH_MIN; import static menu.constant.ErrorMessage.COACH_NAME_LENGTH_ERROR; import static menu.constant.ErrorMessage.NO_EXIST_MENU; import java.util.ArrayList; import java.util.List; +import menu.constant.Constant; public class Coach { @@ -25,7 +27,7 @@ public static Coach from(String name) { } private static void validate(String name) { - if (name.length() < 2 || name.length() > 4) { + if (name.length() < COACH_NAME_LENGTH_MIN || name.length() > Constant.COACH_NAME_LENGTH_MAX) { throw new IllegalArgumentException(COACH_NAME_LENGTH_ERROR.getErrorMessage()); } } @@ -38,10 +40,6 @@ public List getRecommendedMenus() { return recommendedMenus; } - public List getRejectedMenus() { - return rejectedMenus; - } - public void addRejectedMenus(List rejectedMenus) { for (String rejectedMenu : rejectedMenus) { validateExistence(rejectedMenu); @@ -61,7 +59,7 @@ private void validateExistence(String rejectedMenu) { } public boolean possible(String menu) { - return !recommendedMenus.contains(menu) || !rejectedMenus.contains(menu); + return !recommendedMenus.contains(menu) && !rejectedMenus.contains(menu); } public void addRecommendedMenu(String menu) { diff --git a/src/main/java/menu/domain/Recommendation.java b/src/main/java/menu/domain/Recommendation.java index c196a3a8a..b2c28dac5 100644 --- a/src/main/java/menu/domain/Recommendation.java +++ b/src/main/java/menu/domain/Recommendation.java @@ -3,6 +3,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import menu.constant.Constant; +import menu.service.RecommendationDto; public class Recommendation { @@ -19,7 +21,7 @@ public static Recommendation from(Coaches coaches) { } public boolean isDone() { - return categories.size() == 5; + return categories.size() == Constant.RECOMMENDED_MENUS_SIZE; } public boolean possible(String category) { @@ -30,11 +32,7 @@ public void addCategory(String category) { categories.add(category); } - public List getCategories() { - return categories; - } - - public List getCoaches() { - return coaches.getCoaches(); + public RecommendationDto getDto() { + return new RecommendationDto(coaches, categories); } } diff --git a/src/main/java/menu/service/MenuService.java b/src/main/java/menu/service/MenuService.java new file mode 100644 index 000000000..10740ced1 --- /dev/null +++ b/src/main/java/menu/service/MenuService.java @@ -0,0 +1,70 @@ +package menu.service; + +import static menu.constant.Constant.CATEGORIES; + +import java.util.List; +import menu.domain.Coach; +import menu.domain.Coaches; +import menu.domain.Recommendation; +import menu.dto.CoachNamesDto; +import menu.generator.RandomCategoryGenerator; +import menu.generator.RandomMenuGenerator; + +public class MenuService { + + private Coaches coaches; + + public CoachNamesDto registerCoachNames(List coachNames) { + coaches = Coaches.newInstance(); + + for (String coachName : coachNames) { + coaches.addCoach(coachName); + } + + return coaches.getCoachNamesDto(); + } + + public void registerRejectedMenus(String coachName, List rejectedMenus) { + Coach coach = coaches.getCoach(coachName); + coach.addRejectedMenus(rejectedMenus); + } + + public RecommendationDto getResult() { + Recommendation recommendation = Recommendation.from(coaches); + + while (!recommendation.isDone()) { // 추천 끝났으면(추천 카테고리 항목이 5개이면) + String category = RandomCategoryGenerator.generateCategory(); + if (!recommendation.possible(category)) { // 이미 2번 추천 되었으면 + continue; + } + + recommendation.addCategory(category); // 추천 카테고리 추가 + + addRecommendedMenu(category); // 추천 메뉴 추가 + } + + return recommendation.getDto(); + } + + private void addRecommendedMenu(String category) { + for (Coach coach : coaches.getCoaches()) { // 코치별로 메뉴 추천 + addRecommendedMenuEachCoach(category, coach); + } + } + + private void addRecommendedMenuEachCoach(String category, Coach coach) { + while (true) { + List candidateMenus = CATEGORIES.get(category); // 카테고리에 맞는 메뉴 후보 가져오기 + + String menu = RandomMenuGenerator.generateMenu(candidateMenus); + + if (!coach.possible(menu)) { // 이미 추천했거나 못먹는 음식이라면 + continue; + } + + coach.addRecommendedMenu(menu); // 메뉴 추가 + + break; + } + } +} diff --git a/src/main/java/menu/service/RecommendationDto.java b/src/main/java/menu/service/RecommendationDto.java new file mode 100644 index 000000000..1f1f04af3 --- /dev/null +++ b/src/main/java/menu/service/RecommendationDto.java @@ -0,0 +1,23 @@ +package menu.service; + +import java.util.List; +import menu.domain.Coaches; + +public class RecommendationDto { + + private final Coaches coaches; + private final List categories; + + public RecommendationDto(Coaches coaches, List categories) { + this.coaches = coaches; + this.categories = categories; + } + + public List getCategories() { + return categories; + } + + public Coaches getCoaches() { + return coaches; + } +} diff --git a/src/main/java/menu/util/Validator.java b/src/main/java/menu/util/Validator.java index ba22e94e2..96665fdb5 100644 --- a/src/main/java/menu/util/Validator.java +++ b/src/main/java/menu/util/Validator.java @@ -6,6 +6,7 @@ import static menu.constant.ErrorMessage.REJECTED_MENU_COUNT_ERROR; import java.util.List; +import menu.constant.Constant; public final class Validator { @@ -20,11 +21,11 @@ public static void validateCoachNameFormat(String input) { } public static void validateCoachNames(List names) { - if (names.size() < 2) { + if (names.size() < Constant.COACH_COUNT_MIN) { throw new IllegalArgumentException(COACH_COUNT_MIN_ERROR.getErrorMessage()); } - if (names.size() > 5) { + if (names.size() > Constant.COACH_COUNT_MAX) { throw new IllegalArgumentException(COACH_COUNT_MAX_ERROR.getErrorMessage()); } } @@ -40,7 +41,7 @@ public static void validateRejectedMenuFormat(String input) { } public static void validateRejectedMenus(List menus) { - if (menus.size() > 2) { + if (menus.size() > Constant.REJECTED_MENU_COUNT_MAX) { throw new IllegalArgumentException(REJECTED_MENU_COUNT_ERROR.getErrorMessage()); } } diff --git a/src/main/java/menu/view/OutputView.java b/src/main/java/menu/view/OutputView.java index b3e7f8798..4b7fad8f0 100644 --- a/src/main/java/menu/view/OutputView.java +++ b/src/main/java/menu/view/OutputView.java @@ -1,8 +1,17 @@ package menu.view; +import java.util.List; +import menu.domain.Coach; +import menu.domain.Coaches; +import menu.service.RecommendationDto; + public class OutputView { private static final String START_MESSAGE = "점심 메뉴 추천을 시작합니다.\n"; + private static final String RESULT = "메뉴 추천 결과입니다.\n[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ]"; + private static final String CATEGORY_RESULT = "[ 카테고리 | %s ]\n"; + private static final String MENU_RESULT = "[ %s | %s ]\n"; + private static final String FINISH_MESSAGE = "추천을 완료했습니다."; public static void printStart() { System.out.println(START_MESSAGE); @@ -11,4 +20,18 @@ public static void printStart() { public static void printErrorMessage(IllegalArgumentException e) { System.out.println(e.getMessage()); } + + public static void printResult(RecommendationDto result) { + System.out.println(RESULT); + + List categories = result.getCategories(); + System.out.printf(CATEGORY_RESULT, String.join(" | ", categories.toArray(new String[0]))); + + Coaches coaches = result.getCoaches(); + for (Coach coach : coaches.getCoaches()) { + System.out.printf(MENU_RESULT, coach.getName(), String.join(" | ", coach.getRecommendedMenus().toArray(new String[0]))); + } + + System.out.println(FINISH_MESSAGE); + } } From 6804cab17c793e67f4f891fcee2a5f0725a22a60 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 17:34:24 +0900 Subject: [PATCH 09/15] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/menu/constant/ErrorMessage.java | 2 +- src/main/java/menu/domain/Coach.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/menu/constant/ErrorMessage.java b/src/main/java/menu/constant/ErrorMessage.java index 2a8372780..ea4ae55c2 100644 --- a/src/main/java/menu/constant/ErrorMessage.java +++ b/src/main/java/menu/constant/ErrorMessage.java @@ -7,7 +7,7 @@ public enum ErrorMessage { COACH_COUNT_MAX_ERROR("코치는 5명 이하로 입력해야 합니다."), COACH_NAME_LENGTH_ERROR("코치의 이름은 2자 이상 4자 이하여야 합니다."), REJECTED_MENU_COUNT_ERROR("못 먹는 메뉴의 개수는 2개 이하여야 합니다"), - NO_EXIST_MENU("없는 메뉴입니다.") + NO_EXIST_MENU_ERROR("없는 메뉴입니다.") ; private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; diff --git a/src/main/java/menu/domain/Coach.java b/src/main/java/menu/domain/Coach.java index 4208c7c4a..dc7e563e7 100644 --- a/src/main/java/menu/domain/Coach.java +++ b/src/main/java/menu/domain/Coach.java @@ -3,7 +3,7 @@ import static menu.constant.Constant.CATEGORIES; import static menu.constant.Constant.COACH_NAME_LENGTH_MIN; import static menu.constant.ErrorMessage.COACH_NAME_LENGTH_ERROR; -import static menu.constant.ErrorMessage.NO_EXIST_MENU; +import static menu.constant.ErrorMessage.NO_EXIST_MENU_ERROR; import java.util.ArrayList; import java.util.List; @@ -55,7 +55,7 @@ private void validateExistence(String rejectedMenu) { } } - throw new IllegalArgumentException(NO_EXIST_MENU.getErrorMessage()); + throw new IllegalArgumentException(NO_EXIST_MENU_ERROR.getErrorMessage()); } public boolean possible(String menu) { From fa29b560b9d31cc2a604c5cc0549f1b6f4081890 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 17:36:09 +0900 Subject: [PATCH 10/15] =?UTF-8?q?test:=20=EC=98=88=EC=99=B8=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/menu/ApplicationTest.java | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/test/java/menu/ApplicationTest.java b/src/test/java/menu/ApplicationTest.java index a757e7813..2ae364a36 100644 --- a/src/test/java/menu/ApplicationTest.java +++ b/src/test/java/menu/ApplicationTest.java @@ -1,5 +1,6 @@ package menu; +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.mockito.ArgumentMatchers.anyInt; @@ -11,10 +12,13 @@ import java.time.Duration; import java.util.Arrays; import java.util.List; +import menu.constant.ErrorMessage; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.MockedStatic; public class ApplicationTest extends NsTest { @@ -71,6 +75,65 @@ class AllFeatureTest { ); }); } + + @Test + void 코치_이름_형식_오류() { + assertSimpleTest(() -> { + runException("제이미-제이콥"); + assertThat(output()).contains(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + }); + } + + @Test + void 코치_최소_인원_오류() { + assertSimpleTest(() -> { + runException("제이미"); + assertThat(output()).contains(ErrorMessage.COACH_COUNT_MIN_ERROR.getErrorMessage()); + }); + } + + @Test + void 코치_최대_인원_오류() { + assertSimpleTest(() -> { + runException("제이미,제이콥,포비,제임스,메시,사비"); + assertThat(output()).contains(ErrorMessage.COACH_COUNT_MAX_ERROR.getErrorMessage()); + }); + } + + @ParameterizedTest + @ValueSource(strings = {"이니에스타,메시", "홉,메시"}) + void 코치_이름_길이_오류(String input) { + assertSimpleTest(() -> { + runException(input); + assertThat(output()).contains(ErrorMessage.COACH_NAME_LENGTH_ERROR.getErrorMessage()); + }); + } + + @Test + void 코치별_못먹는_메뉴_형식_오류() { + assertSimpleTest(() -> { + runException("제이미,제이콥", "김밥-떡볶이"); + assertThat(output()).contains(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + }); + } + + @Test + void 코치별_못먹는_메뉴_개수_초과_오류() { + assertSimpleTest(() -> { + runException("제이미,제이콥", "김밥,떡볶이,제육볶음"); + assertThat(output()).contains(ErrorMessage.REJECTED_MENU_COUNT_ERROR.getErrorMessage()); + }); + } + + @Test + void 코치별_못먹는_메뉴_존재_오류() { + assertSimpleTest(() -> { + runException("제이미,제이콥", "김밥,해장국"); + assertThat(output()).contains(ErrorMessage.NO_EXIST_MENU_ERROR.getErrorMessage()); + }); + } + + } @Override From c2b6b697b18df588efa7149b6b125fc2b07472d5 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 17:54:15 +0900 Subject: [PATCH 11/15] =?UTF-8?q?test:=20=EC=A0=95=EC=83=81=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/menu/ApplicationTest.java | 145 ++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/src/test/java/menu/ApplicationTest.java b/src/test/java/menu/ApplicationTest.java index 2ae364a36..39dae1b08 100644 --- a/src/test/java/menu/ApplicationTest.java +++ b/src/test/java/menu/ApplicationTest.java @@ -76,6 +76,151 @@ class AllFeatureTest { }); } + @Test + void 못먹는_메뉴_반환시_다시_메뉴_추천_케이스() { + assertTimeoutPreemptively(RANDOM_TEST_TIMEOUT, () -> { + final Executable executable = () -> { + runException("구구,제임스", "김밥", "라자냐"); + + assertThat(output()).contains( + "점심 메뉴 추천을 시작합니다.", + "코치의 이름을 입력해 주세요. (, 로 구분)", + "구구(이)가 못 먹는 메뉴를 입력해 주세요.", + "제임스(이)가 못 먹는 메뉴를 입력해 주세요.", + "메뉴 추천 결과입니다.", + "[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ]", + "[ 카테고리 | 한식 | 양식 | 일식 | 중식 | 아시안 ]", + "[ 구구 | 김치찌개 | 스파게티 | 규동 | 짜장면 | 카오 팟 ]", + "[ 제임스 | 제육볶음 | 그라탱 | 가츠동 | 짬뽕 | 파인애플 볶음밥 ]", + "추천을 완료했습니다." + ); + }; + + assertRandomTest(executable, + Mocking.ofRandomNumberInRange(2, 5, 1, 3, 4), // 숫자는 카테고리 번호를 나타낸다. + Mocking.ofShuffle( + // 월요일 + List.of("김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음"), // 구구 + List.of("김치찌개", "김밥", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음"), // 구구 + List.of("제육볶음", "김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이"), // 제임스 + + // 화요일 + List.of("스파게티", "라자냐", "그라탱", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "피자", "파니니"), // 구구 + List.of("라자냐", "그라탱", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "스파게티", "피자", "파니니"), // 제임스 + List.of("그라탱", "라자냐", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "스파게티", "피자", "파니니"), // 제임스 + + // 수요일 + List.of("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼"), // 구구 + List.of("가츠동", "규동", "우동", "미소시루", "스시", "오니기리", "하이라이스", "라멘", "오코노미야끼"), // 제임스 + + // 목요일 + List.of("짜장면", "깐풍기", "볶음면", "동파육", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채"), // 구구 + List.of("짬뽕", "깐풍기", "볶음면", "동파육", "짜장면", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채"), // 제임스 + + // 금요일 + List.of("카오 팟", "팟타이", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜"), // 구구 + List.of("파인애플 볶음밥", "팟타이", "카오 팟", "나시고렝", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜") // 제임스 + ) + ); + }); + } + + @Test + void 이미_추천한_메뉴_반환시_다시_메뉴_추천_케이스() { + assertTimeoutPreemptively(RANDOM_TEST_TIMEOUT, () -> { + final Executable executable = () -> { + runException("구구,제임스", "김밥", "떡볶이"); + + assertThat(output()).contains( + "점심 메뉴 추천을 시작합니다.", + "코치의 이름을 입력해 주세요. (, 로 구분)", + "구구(이)가 못 먹는 메뉴를 입력해 주세요.", + "제임스(이)가 못 먹는 메뉴를 입력해 주세요.", + "메뉴 추천 결과입니다.", + "[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ]", + "[ 카테고리 | 한식 | 한식 | 일식 | 중식 | 아시안 ]", + "[ 구구 | 김치찌개 | 쌈밥 | 규동 | 짜장면 | 카오 팟 ]", + "[ 제임스 | 제육볶음 | 김치찌개 | 가츠동 | 짬뽕 | 파인애플 볶음밥 ]", + "추천을 완료했습니다." + ); + }; + + assertRandomTest(executable, + Mocking.ofRandomNumberInRange(2, 2, 1, 3, 4), // 숫자는 카테고리 번호를 나타낸다. + Mocking.ofShuffle( + // 월요일 + List.of("김치찌개", "김밥", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음"), // 구구 + List.of("제육볶음", "김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이"), // 제임스 + + // 화요일 + List.of("김치찌개", "김밥", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음"), // 구구 + List.of("쌈밥", "김치찌개", "김밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음"), // 구구 + List.of("김치찌개", "제육볶음", "김밥", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이"), // 제임스 + + // 수요일 + List.of("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼"), // 구구 + List.of("가츠동", "규동", "우동", "미소시루", "스시", "오니기리", "하이라이스", "라멘", "오코노미야끼"), // 제임스 + + // 목요일 + List.of("짜장면", "깐풍기", "볶음면", "동파육", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채"), // 구구 + List.of("짬뽕", "깐풍기", "볶음면", "동파육", "짜장면", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채"), // 제임스 + + // 금요일 + List.of("카오 팟", "팟타이", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜"), // 구구 + List.of("파인애플 볶음밥", "팟타이", "카오 팟", "나시고렝", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜") // 제임스 + ) + ); + }); + } + + @Test + void 이미_2번_추천한_카테고리_반환시_다시_추천_케이스() { + assertTimeoutPreemptively(RANDOM_TEST_TIMEOUT, () -> { + final Executable executable = () -> { + runException("구구,제임스", "김밥", "떡볶이"); + + assertThat(output()).contains( + "점심 메뉴 추천을 시작합니다.", + "코치의 이름을 입력해 주세요. (, 로 구분)", + "구구(이)가 못 먹는 메뉴를 입력해 주세요.", + "제임스(이)가 못 먹는 메뉴를 입력해 주세요.", + "메뉴 추천 결과입니다.", + "[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ]", + "[ 카테고리 | 한식 | 한식 | 일식 | 중식 | 아시안 ]", + "[ 구구 | 김치찌개 | 쌈밥 | 규동 | 짜장면 | 카오 팟 ]", + "[ 제임스 | 제육볶음 | 김치찌개 | 가츠동 | 짬뽕 | 파인애플 볶음밥 ]", + "추천을 완료했습니다." + ); + }; + + assertRandomTest(executable, + Mocking.ofRandomNumberInRange(2, 2, 2, 1, 3, 4), // 숫자는 카테고리 번호를 나타낸다. + Mocking.ofShuffle( + // 월요일 + List.of("김치찌개", "김밥", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음"), // 구구 + List.of("제육볶음", "김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이"), // 제임스 + + // 화요일 + List.of("김치찌개", "김밥", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음"), // 구구 + List.of("쌈밥", "김치찌개", "김밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음"), // 구구 + List.of("김치찌개", "제육볶음", "김밥", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이"), // 제임스 + + // 수요일 + List.of("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼"), // 구구 + List.of("가츠동", "규동", "우동", "미소시루", "스시", "오니기리", "하이라이스", "라멘", "오코노미야끼"), // 제임스 + + // 목요일 + List.of("짜장면", "깐풍기", "볶음면", "동파육", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채"), // 구구 + List.of("짬뽕", "깐풍기", "볶음면", "동파육", "짜장면", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채"), // 제임스 + + // 금요일 + List.of("카오 팟", "팟타이", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜"), // 구구 + List.of("파인애플 볶음밥", "팟타이", "카오 팟", "나시고렝", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜") // 제임스 + ) + ); + }); + } + @Test void 코치_이름_형식_오류() { assertSimpleTest(() -> { From f39ab98df2e83a46f2a039064cb2f555c89b1d30 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 17:54:37 +0900 Subject: [PATCH 12/15] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A6=84=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=98=88=EC=99=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/menu/constant/ErrorMessage.java | 4 +++- src/main/java/menu/domain/Coaches.java | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/menu/constant/ErrorMessage.java b/src/main/java/menu/constant/ErrorMessage.java index ea4ae55c2..4c5efbd19 100644 --- a/src/main/java/menu/constant/ErrorMessage.java +++ b/src/main/java/menu/constant/ErrorMessage.java @@ -7,7 +7,9 @@ public enum ErrorMessage { COACH_COUNT_MAX_ERROR("코치는 5명 이하로 입력해야 합니다."), COACH_NAME_LENGTH_ERROR("코치의 이름은 2자 이상 4자 이하여야 합니다."), REJECTED_MENU_COUNT_ERROR("못 먹는 메뉴의 개수는 2개 이하여야 합니다"), - NO_EXIST_MENU_ERROR("없는 메뉴입니다.") + NO_EXIST_MENU_ERROR("없는 메뉴입니다."), + NAME_UNIQUE_ERROR("중복된 이름이 있습니다."), + MENU_UNIQUE_ERROR("중복된 이름이 있습니다."), ; private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; diff --git a/src/main/java/menu/domain/Coaches.java b/src/main/java/menu/domain/Coaches.java index 52b7b7253..1f8e41661 100644 --- a/src/main/java/menu/domain/Coaches.java +++ b/src/main/java/menu/domain/Coaches.java @@ -1,5 +1,7 @@ package menu.domain; +import static menu.constant.ErrorMessage.NAME_UNIQUE_ERROR; + import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -19,6 +21,11 @@ public static Coaches newInstance() { public void addCoach(String coachName) { Coach coach = Coach.from(coachName); + + if (coaches.contains(coach)) { + throw new IllegalArgumentException(NAME_UNIQUE_ERROR.getErrorMessage()); + } + coaches.add(coach); } From 6889ab8b0cf4db4bcfe5cdef0e1ef204eeb5b92e Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 21 Dec 2025 17:56:05 +0900 Subject: [PATCH 13/15] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 923ee58aa..8de42de83 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ - “코치는 최대 5명 이하로 입력해야 합니다.” 3. 코치의 이름이 2자 미만 4자 초과면 예외 발생 - “코치의 이름은 2자 이상 4자 이하여야 합니다.” + 4. 코치의 이름에 중복이 있으면 예외 발생 + - "중복된 이름이 있습니다." 3. 코치 별 못 먹는 메뉴 입력 - “%s(이)가 못 먹는 메뉴를 입력해 주세요.” - 예외 사항 @@ -20,6 +22,8 @@ - “입력 형식이 올바르지 않습니다.” 2. 메뉴 개수가 2개 초과인 경우 예외 발생 - “못 먹는 메뉴의 개수는 2개 이하여야 합니다.” + 3. 메뉴에 중복이 있으면 예외 발생 + - "중복된 메뉴가 있습니다." 4. 요일별(월~금 순서대로) 카테고리를 뽑는다. - 만약 이미 2번 뽑힌 카테고리가 나온다면 다시 뽑는다. 1. 코치별 메뉴를 뽑는다. From 55117882629e02e32c70180901c51e37d48c37e7 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 22 Dec 2025 17:56:29 +0900 Subject: [PATCH 14/15] =?UTF-8?q?refactor:=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C=20=EB=AC=B8=EC=A0=9C=20=ED=92=80=EC=9D=B4=20?= =?UTF-8?q?=EC=9D=B4=ED=9B=84=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/menu/constant/Constant.java | 26 ---------- src/main/java/menu/constant/ErrorMessage.java | 3 +- src/main/java/menu/constant/Menu.java | 52 +++++++++++++++++++ .../java/menu/controller/MenuController.java | 49 ++++++++++------- src/main/java/menu/domain/Coach.java | 21 ++++---- src/main/java/menu/domain/Coaches.java | 18 +------ src/main/java/menu/domain/Recommendation.java | 18 ++++--- src/main/java/menu/dto/CoachNamesDto.java | 17 ------ .../generator/RandomCategoryGenerator.java | 7 ++- src/main/java/menu/service/MenuService.java | 32 +++++------- .../java/menu/service/RecommendationDto.java | 23 -------- src/main/java/menu/util/InputParser.java | 2 + src/main/java/menu/util/Validator.java | 34 ++++++------ src/main/java/menu/view/InputView.java | 1 - src/main/java/menu/view/OutputView.java | 4 +- src/test/java/menu/ApplicationTest.java | 17 +++++- 16 files changed, 159 insertions(+), 165 deletions(-) delete mode 100644 src/main/java/menu/constant/Constant.java create mode 100644 src/main/java/menu/constant/Menu.java delete mode 100644 src/main/java/menu/dto/CoachNamesDto.java delete mode 100644 src/main/java/menu/service/RecommendationDto.java diff --git a/src/main/java/menu/constant/Constant.java b/src/main/java/menu/constant/Constant.java deleted file mode 100644 index f20f8289e..000000000 --- a/src/main/java/menu/constant/Constant.java +++ /dev/null @@ -1,26 +0,0 @@ -package menu.constant; - -import java.util.List; -import java.util.Map; - -public final class Constant { - - public static final List CATEGORY_NAMES = List.of("일식", "한식", "중식", "아시안", "양식"); - public static final Map> CATEGORIES = Map.of( - "일식", List.of("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼"), - "한식", List.of("김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음"), - "중식", List.of("깐풍기", "볶음면", "동파육", "짜장면", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채"), - "아시안", List.of("팟타이", "카오 팟", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜"), - "양식", List.of("라자냐", "그라탱", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "스파게티", "피자", "파니니") - ); - - public static final int COACH_NAME_LENGTH_MIN = 2; - public static final int COACH_NAME_LENGTH_MAX = 4; - - public static final int RECOMMENDED_MENUS_SIZE = 5; - - public static final int COACH_COUNT_MIN = 2; - public static final int COACH_COUNT_MAX = 5; - - public static final int REJECTED_MENU_COUNT_MAX = 2; -} diff --git a/src/main/java/menu/constant/ErrorMessage.java b/src/main/java/menu/constant/ErrorMessage.java index 4c5efbd19..17a620682 100644 --- a/src/main/java/menu/constant/ErrorMessage.java +++ b/src/main/java/menu/constant/ErrorMessage.java @@ -8,8 +8,7 @@ public enum ErrorMessage { COACH_NAME_LENGTH_ERROR("코치의 이름은 2자 이상 4자 이하여야 합니다."), REJECTED_MENU_COUNT_ERROR("못 먹는 메뉴의 개수는 2개 이하여야 합니다"), NO_EXIST_MENU_ERROR("없는 메뉴입니다."), - NAME_UNIQUE_ERROR("중복된 이름이 있습니다."), - MENU_UNIQUE_ERROR("중복된 이름이 있습니다."), + UNIQUE_ERROR("중복된 값이 있습니다."), ; private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; diff --git a/src/main/java/menu/constant/Menu.java b/src/main/java/menu/constant/Menu.java new file mode 100644 index 000000000..866040aaf --- /dev/null +++ b/src/main/java/menu/constant/Menu.java @@ -0,0 +1,52 @@ +package menu.constant; + +import java.util.List; + +public enum Menu { + JAPANESE(1, "일식", List.of("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼")), + KOREAN(2, "한식", List.of("김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음")), + CHINESE(3, "중식", List.of("깐풍기", "볶음면", "동파육", "짜장면", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채")), + ASIAN(4, "아시안", List.of("팟타이", "카오 팟", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜")), + WESTERN(5, "양식", List.of("라자냐", "그라탱", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "스파게티", "피자", "파니니")), + ; + + private final int index; + private final String category; + private final List menus; + + Menu(int index, String category, List menus) { + this.index = index; + this.category = category; + this.menus = menus; + } + + public static Menu fromIndex(int index) { + for (Menu menu : Menu.values()) { + if (menu.index == index) { + return menu; + } + } + throw new IllegalArgumentException(); + } + + public static Menu fromCategory(String category) { + for (Menu menu : Menu.values()) { + if (menu.category.equals(category)) { + return menu; + } + } + throw new IllegalArgumentException(); + } + + public int getIndex() { + return index; + } + + public String getCategory() { + return category; + } + + public List getMenus() { + return menus; + } +} diff --git a/src/main/java/menu/controller/MenuController.java b/src/main/java/menu/controller/MenuController.java index 1c47513b9..cf3746c10 100644 --- a/src/main/java/menu/controller/MenuController.java +++ b/src/main/java/menu/controller/MenuController.java @@ -1,9 +1,11 @@ package menu.controller; import java.util.List; -import menu.dto.CoachNamesDto; +import java.util.function.Supplier; +import menu.domain.Coach; +import menu.domain.Coaches; +import menu.domain.Recommendation; import menu.service.MenuService; -import menu.service.RecommendationDto; import menu.util.InputParser; import menu.view.InputView; import menu.view.OutputView; @@ -19,39 +21,48 @@ public MenuController(MenuService menuService) { public void run() { OutputView.printStart(); - CoachNamesDto coachNamesDto = registerCoachNames(); - List coachNames = coachNamesDto.getCoachNames(); - - for (String coachName : coachNames) { - registerRejectedMenus(coachName); + Coaches coaches = registerCoachNames(); + for (Coach coach : coaches.getCoaches()) { + registerRejectedMenus(coach.getName()); } - RecommendationDto result = menuService.getResult(); + Recommendation result = menuService.getResult(); OutputView.printResult(result); } private void registerRejectedMenus(String coachName) { - while (true) { - try { - String readRejectedMenus = InputView.readRejectedMenus(coachName); - List rejectedMenus = InputParser.parseRejectedMenus(readRejectedMenus); + retryOnError(() -> { + String readRejectedMenus = InputView.readRejectedMenus(coachName); + List rejectedMenus = InputParser.parseRejectedMenus(readRejectedMenus); - menuService.registerRejectedMenus(coachName, rejectedMenus); + menuService.registerRejectedMenus(coachName, rejectedMenus); + }); + } - return; + private Coaches registerCoachNames() { + return retryOnError(() -> { + String readCoachNames = InputView.readCoachNames(); + List coachNames = InputParser.parseCoachNames(readCoachNames); + + return menuService.registerCoachNames(coachNames); + }); + } + + private T retryOnError(Supplier supplier) { + while (true) { + try { + return supplier.get(); } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e); } } } - private CoachNamesDto registerCoachNames() { + private void retryOnError(Runnable runnable) { while (true) { try { - String readCoachNames = InputView.readCoachNames(); - List coachNames = InputParser.parseCoachNames(readCoachNames); - - return menuService.registerCoachNames(coachNames); + runnable.run(); + return; } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e); } diff --git a/src/main/java/menu/domain/Coach.java b/src/main/java/menu/domain/Coach.java index dc7e563e7..22e2613ba 100644 --- a/src/main/java/menu/domain/Coach.java +++ b/src/main/java/menu/domain/Coach.java @@ -1,16 +1,15 @@ package menu.domain; -import static menu.constant.Constant.CATEGORIES; -import static menu.constant.Constant.COACH_NAME_LENGTH_MIN; -import static menu.constant.ErrorMessage.COACH_NAME_LENGTH_ERROR; -import static menu.constant.ErrorMessage.NO_EXIST_MENU_ERROR; - import java.util.ArrayList; import java.util.List; -import menu.constant.Constant; +import menu.constant.ErrorMessage; +import menu.constant.Menu; public class Coach { + public static final int COACH_NAME_LENGTH_MIN = 2; + public static final int COACH_NAME_LENGTH_MAX = 4; + private final String name; private List rejectedMenus; private final List recommendedMenus; @@ -27,8 +26,8 @@ public static Coach from(String name) { } private static void validate(String name) { - if (name.length() < COACH_NAME_LENGTH_MIN || name.length() > Constant.COACH_NAME_LENGTH_MAX) { - throw new IllegalArgumentException(COACH_NAME_LENGTH_ERROR.getErrorMessage()); + if (name.length() < COACH_NAME_LENGTH_MIN || name.length() > COACH_NAME_LENGTH_MAX) { + throw new IllegalArgumentException(ErrorMessage.COACH_NAME_LENGTH_ERROR.getErrorMessage()); } } @@ -48,14 +47,14 @@ public void addRejectedMenus(List rejectedMenus) { } private void validateExistence(String rejectedMenu) { - for (String categoryName : CATEGORIES.keySet()) { - List menus = CATEGORIES.get(categoryName); + for (Menu menu : Menu.values()) { + List menus = menu.getMenus(); if (menus.contains(rejectedMenu)) { return; } } - throw new IllegalArgumentException(NO_EXIST_MENU_ERROR.getErrorMessage()); + throw new IllegalArgumentException(ErrorMessage.NO_EXIST_MENU_ERROR.getErrorMessage()); } public boolean possible(String menu) { diff --git a/src/main/java/menu/domain/Coaches.java b/src/main/java/menu/domain/Coaches.java index 1f8e41661..7058c55fb 100644 --- a/src/main/java/menu/domain/Coaches.java +++ b/src/main/java/menu/domain/Coaches.java @@ -1,11 +1,7 @@ package menu.domain; -import static menu.constant.ErrorMessage.NAME_UNIQUE_ERROR; - import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import menu.dto.CoachNamesDto; public class Coaches { @@ -21,28 +17,16 @@ public static Coaches newInstance() { public void addCoach(String coachName) { Coach coach = Coach.from(coachName); - - if (coaches.contains(coach)) { - throw new IllegalArgumentException(NAME_UNIQUE_ERROR.getErrorMessage()); - } - coaches.add(coach); } - public CoachNamesDto getCoachNamesDto() { - List coachNames = coaches.stream() - .map(Coach::getName) - .collect(Collectors.toList()); - return new CoachNamesDto(coachNames); - } - public Coach getCoach(String coachName) { for (Coach coach : coaches) { if (coach.getName().equals(coachName)) { return coach; } } - return null; + throw new IllegalArgumentException(); } public List getCoaches() { diff --git a/src/main/java/menu/domain/Recommendation.java b/src/main/java/menu/domain/Recommendation.java index b2c28dac5..003473f09 100644 --- a/src/main/java/menu/domain/Recommendation.java +++ b/src/main/java/menu/domain/Recommendation.java @@ -3,11 +3,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import menu.constant.Constant; -import menu.service.RecommendationDto; public class Recommendation { + public static final int RECOMMENDED_MENUS_SIZE = 5; + private final Coaches coaches; private final List categories; @@ -21,18 +21,22 @@ public static Recommendation from(Coaches coaches) { } public boolean isDone() { - return categories.size() == Constant.RECOMMENDED_MENUS_SIZE; + return categories.size() == RECOMMENDED_MENUS_SIZE; } - public boolean possible(String category) { - return Collections.frequency(categories, category) != 2; + public boolean isPossible(String category) { + return Collections.frequency(categories, category) < 2; } public void addCategory(String category) { categories.add(category); } - public RecommendationDto getDto() { - return new RecommendationDto(coaches, categories); + public Coaches getCoaches() { + return coaches; + } + + public List getCategories() { + return categories; } } diff --git a/src/main/java/menu/dto/CoachNamesDto.java b/src/main/java/menu/dto/CoachNamesDto.java deleted file mode 100644 index 4fe30abcc..000000000 --- a/src/main/java/menu/dto/CoachNamesDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package menu.dto; - -import java.util.ArrayList; -import java.util.List; - -public class CoachNamesDto { - - private final List coachNames; - - public CoachNamesDto(List coachNames) { - this.coachNames = new ArrayList<>(coachNames); - } - - public List getCoachNames() { - return coachNames; - } -} diff --git a/src/main/java/menu/generator/RandomCategoryGenerator.java b/src/main/java/menu/generator/RandomCategoryGenerator.java index 1c10be58a..7d6546f22 100644 --- a/src/main/java/menu/generator/RandomCategoryGenerator.java +++ b/src/main/java/menu/generator/RandomCategoryGenerator.java @@ -1,13 +1,12 @@ package menu.generator; -import static menu.constant.Constant.CATEGORY_NAMES; - import camp.nextstep.edu.missionutils.Randoms; +import menu.constant.Menu; public final class RandomCategoryGenerator { public static String generateCategory() { - int number = Randoms.pickNumberInRange(1, 5); - return CATEGORY_NAMES.get(number - 1); + int index = Randoms.pickNumberInRange(1, 5); + return Menu.fromIndex(index).getCategory(); } } diff --git a/src/main/java/menu/service/MenuService.java b/src/main/java/menu/service/MenuService.java index 10740ced1..667210d78 100644 --- a/src/main/java/menu/service/MenuService.java +++ b/src/main/java/menu/service/MenuService.java @@ -1,12 +1,10 @@ package menu.service; -import static menu.constant.Constant.CATEGORIES; - import java.util.List; +import menu.constant.Menu; import menu.domain.Coach; import menu.domain.Coaches; import menu.domain.Recommendation; -import menu.dto.CoachNamesDto; import menu.generator.RandomCategoryGenerator; import menu.generator.RandomMenuGenerator; @@ -14,14 +12,14 @@ public class MenuService { private Coaches coaches; - public CoachNamesDto registerCoachNames(List coachNames) { + public Coaches registerCoachNames(List coachNames) { coaches = Coaches.newInstance(); for (String coachName : coachNames) { coaches.addCoach(coachName); } - return coaches.getCoachNamesDto(); + return coaches; } public void registerRejectedMenus(String coachName, List rejectedMenus) { @@ -29,21 +27,18 @@ public void registerRejectedMenus(String coachName, List rejectedMenus) coach.addRejectedMenus(rejectedMenus); } - public RecommendationDto getResult() { + public Recommendation getResult() { Recommendation recommendation = Recommendation.from(coaches); while (!recommendation.isDone()) { // 추천 끝났으면(추천 카테고리 항목이 5개이면) String category = RandomCategoryGenerator.generateCategory(); - if (!recommendation.possible(category)) { // 이미 2번 추천 되었으면 - continue; + if (recommendation.isPossible(category)) { // 2번 미만으로 추천 되었으면 + recommendation.addCategory(category); // 추천 카테고리 추가 + addRecommendedMenu(category); // 추천 메뉴 추가 } - - recommendation.addCategory(category); // 추천 카테고리 추가 - - addRecommendedMenu(category); // 추천 메뉴 추가 } - return recommendation.getDto(); + return recommendation; } private void addRecommendedMenu(String category) { @@ -54,17 +49,14 @@ private void addRecommendedMenu(String category) { private void addRecommendedMenuEachCoach(String category, Coach coach) { while (true) { - List candidateMenus = CATEGORIES.get(category); // 카테고리에 맞는 메뉴 후보 가져오기 + List candidateMenus = Menu.fromCategory(category).getMenus(); // 카테고리에 맞는 메뉴 후보 가져오기 String menu = RandomMenuGenerator.generateMenu(candidateMenus); - if (!coach.possible(menu)) { // 이미 추천했거나 못먹는 음식이라면 - continue; + if (coach.possible(menu)) { // 이미 추천했거나 못먹는 음식이라면 + coach.addRecommendedMenu(menu); // 메뉴 추가 + break; } - - coach.addRecommendedMenu(menu); // 메뉴 추가 - - break; } } } diff --git a/src/main/java/menu/service/RecommendationDto.java b/src/main/java/menu/service/RecommendationDto.java deleted file mode 100644 index 1f1f04af3..000000000 --- a/src/main/java/menu/service/RecommendationDto.java +++ /dev/null @@ -1,23 +0,0 @@ -package menu.service; - -import java.util.List; -import menu.domain.Coaches; - -public class RecommendationDto { - - private final Coaches coaches; - private final List categories; - - public RecommendationDto(Coaches coaches, List categories) { - this.coaches = coaches; - this.categories = categories; - } - - public List getCategories() { - return categories; - } - - public Coaches getCoaches() { - return coaches; - } -} diff --git a/src/main/java/menu/util/InputParser.java b/src/main/java/menu/util/InputParser.java index 85e1390c8..01431e053 100644 --- a/src/main/java/menu/util/InputParser.java +++ b/src/main/java/menu/util/InputParser.java @@ -24,6 +24,7 @@ public static List parseCoachNames(String rawInput) { List names = parseToElements(rawInput); + Validator.validateUnique(names); Validator.validateCoachNames(names); return names; @@ -34,6 +35,7 @@ public static List parseRejectedMenus(String rawInput) { List menus = parseToElements(rawInput); + Validator.validateUnique(menus); Validator.validateRejectedMenus(menus); return menus; diff --git a/src/main/java/menu/util/Validator.java b/src/main/java/menu/util/Validator.java index 96665fdb5..4657af632 100644 --- a/src/main/java/menu/util/Validator.java +++ b/src/main/java/menu/util/Validator.java @@ -1,48 +1,52 @@ package menu.util; -import static menu.constant.ErrorMessage.COACH_COUNT_MAX_ERROR; -import static menu.constant.ErrorMessage.COACH_COUNT_MIN_ERROR; -import static menu.constant.ErrorMessage.FORMAT_ERROR; -import static menu.constant.ErrorMessage.REJECTED_MENU_COUNT_ERROR; - import java.util.List; -import menu.constant.Constant; +import menu.constant.ErrorMessage; public final class Validator { private static final String CSV_FORMAT = "^ *[가-힣a-zA-Z]+ *(, *[가-힣a-zA-Z]+ *)*$"; + public static final int COACH_COUNT_MIN = 2; + public static final int COACH_COUNT_MAX = 5; + public static final int REJECTED_MENU_COUNT_MAX = 2; private Validator() {} public static void validateCoachNameFormat(String input) { if (!input.matches(CSV_FORMAT)) { - throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); + throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); } } public static void validateCoachNames(List names) { - if (names.size() < Constant.COACH_COUNT_MIN) { - throw new IllegalArgumentException(COACH_COUNT_MIN_ERROR.getErrorMessage()); + if (names.size() < COACH_COUNT_MIN) { + throw new IllegalArgumentException(ErrorMessage.COACH_COUNT_MIN_ERROR.getErrorMessage()); } - if (names.size() > Constant.COACH_COUNT_MAX) { - throw new IllegalArgumentException(COACH_COUNT_MAX_ERROR.getErrorMessage()); + if (names.size() > COACH_COUNT_MAX) { + throw new IllegalArgumentException(ErrorMessage.COACH_COUNT_MAX_ERROR.getErrorMessage()); } } public static void validateRejectedMenuFormat(String input) { if (input.isBlank()) { return; - } + } // 테스트 필요 if (!input.matches(CSV_FORMAT)) { - throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); + throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); } } public static void validateRejectedMenus(List menus) { - if (menus.size() > Constant.REJECTED_MENU_COUNT_MAX) { - throw new IllegalArgumentException(REJECTED_MENU_COUNT_ERROR.getErrorMessage()); + if (menus.size() > REJECTED_MENU_COUNT_MAX) { + throw new IllegalArgumentException(ErrorMessage.REJECTED_MENU_COUNT_ERROR.getErrorMessage()); + } + } + + public static void validateUnique(List inputs) { + if (inputs.stream().distinct().count() != inputs.size()) { + throw new IllegalArgumentException(ErrorMessage.UNIQUE_ERROR.getErrorMessage()); } } } diff --git a/src/main/java/menu/view/InputView.java b/src/main/java/menu/view/InputView.java index d662e9457..45c526262 100644 --- a/src/main/java/menu/view/InputView.java +++ b/src/main/java/menu/view/InputView.java @@ -1,7 +1,6 @@ package menu.view; import camp.nextstep.edu.missionutils.Console; -import menu.domain.Coach; public class InputView { diff --git a/src/main/java/menu/view/OutputView.java b/src/main/java/menu/view/OutputView.java index 4b7fad8f0..a485690eb 100644 --- a/src/main/java/menu/view/OutputView.java +++ b/src/main/java/menu/view/OutputView.java @@ -3,7 +3,7 @@ import java.util.List; import menu.domain.Coach; import menu.domain.Coaches; -import menu.service.RecommendationDto; +import menu.domain.Recommendation; public class OutputView { @@ -21,7 +21,7 @@ public static void printErrorMessage(IllegalArgumentException e) { System.out.println(e.getMessage()); } - public static void printResult(RecommendationDto result) { + public static void printResult(Recommendation result) { System.out.println(RESULT); List categories = result.getCategories(); diff --git a/src/test/java/menu/ApplicationTest.java b/src/test/java/menu/ApplicationTest.java index 39dae1b08..8e5c9b550 100644 --- a/src/test/java/menu/ApplicationTest.java +++ b/src/test/java/menu/ApplicationTest.java @@ -254,6 +254,15 @@ class AllFeatureTest { }); } + @ParameterizedTest + @ValueSource(strings = {"메시,메시"}) + void 코치_이름_중복_오류(String input) { + assertSimpleTest(() -> { + runException(input); + assertThat(output()).contains(ErrorMessage.UNIQUE_ERROR.getErrorMessage()); + }); + } + @Test void 코치별_못먹는_메뉴_형식_오류() { assertSimpleTest(() -> { @@ -278,7 +287,13 @@ class AllFeatureTest { }); } - + @Test + void 코치별_못먹는_메뉴_중복_오류() { + assertSimpleTest(() -> { + runException("제이미,제이콥", "김밥,김밥"); + assertThat(output()).contains(ErrorMessage.UNIQUE_ERROR.getErrorMessage()); + }); + } } @Override From e53b19d65eacbbf61a8c40cc633aadd6f66ee9b3 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 1 Jan 2026 19:55:51 +0900 Subject: [PATCH 15/15] =?UTF-8?q?refactor:=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C=20=EB=AC=B8=EC=A0=9C=20=ED=92=80=EC=9D=B4=20?= =?UTF-8?q?=EC=9D=B4=ED=9B=84=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../constant/{Menu.java => Category.java} | 41 ++++++++----------- src/main/java/menu/constant/ErrorMessage.java | 1 + src/main/java/menu/domain/Coach.java | 7 ++-- .../generator/RandomCategoryGenerator.java | 4 +- src/main/java/menu/service/MenuService.java | 9 ++-- src/main/java/menu/util/InputParser.java | 4 ++ src/main/java/menu/util/Validator.java | 7 +--- 7 files changed, 34 insertions(+), 39 deletions(-) rename src/main/java/menu/constant/{Menu.java => Category.java} (56%) diff --git a/src/main/java/menu/constant/Menu.java b/src/main/java/menu/constant/Category.java similarity index 56% rename from src/main/java/menu/constant/Menu.java rename to src/main/java/menu/constant/Category.java index 866040aaf..f04a321c8 100644 --- a/src/main/java/menu/constant/Menu.java +++ b/src/main/java/menu/constant/Category.java @@ -1,8 +1,9 @@ package menu.constant; +import java.util.Arrays; import java.util.List; -public enum Menu { +public enum Category { JAPANESE(1, "일식", List.of("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼")), KOREAN(2, "한식", List.of("김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음")), CHINESE(3, "중식", List.of("깐풍기", "볶음면", "동파육", "짜장면", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채")), @@ -11,39 +12,31 @@ public enum Menu { ; private final int index; - private final String category; + private final String name; private final List menus; - Menu(int index, String category, List menus) { + Category(int index, String name, List menus) { this.index = index; - this.category = category; + this.name = name; this.menus = menus; } - public static Menu fromIndex(int index) { - for (Menu menu : Menu.values()) { - if (menu.index == index) { - return menu; - } - } - throw new IllegalArgumentException(); + public static Category fromIndex(int index) { + return Arrays.stream(values()) + .filter(category -> category.index == index) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_EXIST_CATEGORY_ERROR.getErrorMessage())); } - public static Menu fromCategory(String category) { - for (Menu menu : Menu.values()) { - if (menu.category.equals(category)) { - return menu; - } - } - throw new IllegalArgumentException(); + public static Category fromName(String name) { + return Arrays.stream(values()) + .filter(category -> category.name.equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_EXIST_CATEGORY_ERROR.getErrorMessage())); } - public int getIndex() { - return index; - } - - public String getCategory() { - return category; + public String getName() { + return name; } public List getMenus() { diff --git a/src/main/java/menu/constant/ErrorMessage.java b/src/main/java/menu/constant/ErrorMessage.java index 17a620682..eaf707656 100644 --- a/src/main/java/menu/constant/ErrorMessage.java +++ b/src/main/java/menu/constant/ErrorMessage.java @@ -9,6 +9,7 @@ public enum ErrorMessage { REJECTED_MENU_COUNT_ERROR("못 먹는 메뉴의 개수는 2개 이하여야 합니다"), NO_EXIST_MENU_ERROR("없는 메뉴입니다."), UNIQUE_ERROR("중복된 값이 있습니다."), + NO_EXIST_CATEGORY_ERROR("없는 카테고리 입니다."), ; private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; diff --git a/src/main/java/menu/domain/Coach.java b/src/main/java/menu/domain/Coach.java index 22e2613ba..7a30a5dfc 100644 --- a/src/main/java/menu/domain/Coach.java +++ b/src/main/java/menu/domain/Coach.java @@ -3,7 +3,7 @@ import java.util.ArrayList; import java.util.List; import menu.constant.ErrorMessage; -import menu.constant.Menu; +import menu.constant.Category; public class Coach { @@ -16,6 +16,7 @@ public class Coach { private Coach(String name) { this.name = name; + this.rejectedMenus = new ArrayList<>(); this.recommendedMenus = new ArrayList<>(); } @@ -47,7 +48,7 @@ public void addRejectedMenus(List rejectedMenus) { } private void validateExistence(String rejectedMenu) { - for (Menu menu : Menu.values()) { + for (Category menu : Category.values()) { List menus = menu.getMenus(); if (menus.contains(rejectedMenu)) { return; @@ -57,7 +58,7 @@ private void validateExistence(String rejectedMenu) { throw new IllegalArgumentException(ErrorMessage.NO_EXIST_MENU_ERROR.getErrorMessage()); } - public boolean possible(String menu) { + public boolean isPossible(String menu) { return !recommendedMenus.contains(menu) && !rejectedMenus.contains(menu); } diff --git a/src/main/java/menu/generator/RandomCategoryGenerator.java b/src/main/java/menu/generator/RandomCategoryGenerator.java index 7d6546f22..96411a548 100644 --- a/src/main/java/menu/generator/RandomCategoryGenerator.java +++ b/src/main/java/menu/generator/RandomCategoryGenerator.java @@ -1,12 +1,12 @@ package menu.generator; import camp.nextstep.edu.missionutils.Randoms; -import menu.constant.Menu; +import menu.constant.Category; public final class RandomCategoryGenerator { public static String generateCategory() { int index = Randoms.pickNumberInRange(1, 5); - return Menu.fromIndex(index).getCategory(); + return Category.fromIndex(index).getName(); } } diff --git a/src/main/java/menu/service/MenuService.java b/src/main/java/menu/service/MenuService.java index 667210d78..21a4ab209 100644 --- a/src/main/java/menu/service/MenuService.java +++ b/src/main/java/menu/service/MenuService.java @@ -1,7 +1,7 @@ package menu.service; import java.util.List; -import menu.constant.Menu; +import menu.constant.Category; import menu.domain.Coach; import menu.domain.Coaches; import menu.domain.Recommendation; @@ -49,11 +49,10 @@ private void addRecommendedMenu(String category) { private void addRecommendedMenuEachCoach(String category, Coach coach) { while (true) { - List candidateMenus = Menu.fromCategory(category).getMenus(); // 카테고리에 맞는 메뉴 후보 가져오기 + List candidateMenus = Category.fromName(category).getMenus(); // 카테고리에 맞는 메뉴 후보 가져오기 + String menu = RandomMenuGenerator.generateMenu(candidateMenus); // 후보 중 메뉴 하나 랜덤 선택 - String menu = RandomMenuGenerator.generateMenu(candidateMenus); - - if (coach.possible(menu)) { // 이미 추천했거나 못먹는 음식이라면 + if (coach.isPossible(menu)) { // 이미 추천했거나 못먹는 음식이라면 coach.addRecommendedMenu(menu); // 메뉴 추가 break; } diff --git a/src/main/java/menu/util/InputParser.java b/src/main/java/menu/util/InputParser.java index 01431e053..6baa0336a 100644 --- a/src/main/java/menu/util/InputParser.java +++ b/src/main/java/menu/util/InputParser.java @@ -31,6 +31,10 @@ public static List parseCoachNames(String rawInput) { } public static List parseRejectedMenus(String rawInput) { + if (rawInput.isBlank()) { + return List.of(); + } + Validator.validateRejectedMenuFormat(rawInput.strip()); List menus = parseToElements(rawInput); diff --git a/src/main/java/menu/util/Validator.java b/src/main/java/menu/util/Validator.java index 4657af632..548d7751c 100644 --- a/src/main/java/menu/util/Validator.java +++ b/src/main/java/menu/util/Validator.java @@ -1,5 +1,6 @@ package menu.util; +import java.util.HashSet; import java.util.List; import menu.constant.ErrorMessage; @@ -29,10 +30,6 @@ public static void validateCoachNames(List names) { } public static void validateRejectedMenuFormat(String input) { - if (input.isBlank()) { - return; - } // 테스트 필요 - if (!input.matches(CSV_FORMAT)) { throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); } @@ -45,7 +42,7 @@ public static void validateRejectedMenus(List menus) { } public static void validateUnique(List inputs) { - if (inputs.stream().distinct().count() != inputs.size()) { + if (inputs.size() != new HashSet<>(inputs).size()) { throw new IllegalArgumentException(ErrorMessage.UNIQUE_ERROR.getErrorMessage()); } }