diff --git a/docs/README.md b/docs/README.md index e69de29bb..8f01e3a7b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,106 @@ +## 기능 목록 +- 기능 목록 +- [x] 시작 메시지 출력 +- [x] 코치 이름 + - [x] 코치 이름 입력 요청 메시지 출력 + - [x] 코치 이름 입력 + - [x] 콤마로 구분해 입력했는지 검증 + - [x] 코치 이름 2글자 ~ 4글자인지 검증 + - [x] 코치가 2명 이상인지 검증 + - [x] 잘못된 입력시 재입력 +- [x] 코치가 못 먹는 메뉴 + - [x] 코치 못 먹는 메뉴 입력 요청 메시지 출력 + - [x] 코치 못 먹는 메뉴 입력 + - [x] 콤마로 구분해 입력했는지 검증 + - [x] 개수가 0 ~ 2개 인지 검증 + - [x] 잘못된 입력시 재입력 +- [x] 메뉴 추천 + - [x] 이전에 먹은 음식은 못먹도록 필터링 + - [x] 같은 카테고리 2번만 먹을 수 있도록 필터링 + - [x] 못 먹는 음식은 추천하지 않도록 필터링 + - [x] 랜덤으로 셔플 + - [x] 메뉴 추천 메시지 출력 + - [x] 추천 완료 메시지 출력 + +## 구현 클래스 목록 +- MenuController + - start() + +- ComponentFactory + - menuController() + +- OutputView + - printStart() + - printCoachNameRequest() + +- CoachList + - getNextCoach() + - getCoachNum() + +- InputManager + - readCoach() + - readCoachMenu() + +- InputValidator + - validateSplitter() + - validateCoachMenu() + +- InputView + - readCoach() + - readCoachMenu() + +- MenuService + - recommendMenu() + - saveCoachList() + - addHateMenu() + +- MenuRepository + - saveCoachList() + - findCoachList() + - saveCoachMenuMap() + - findCoachMenuMap() + +- MenuMap + - getInstance() + +- MenuGenerator + - generate() + +- AvailMenuList + - getMenuByCategory() + +- Coach + - getName() + - addMenus() + - getAvailMenuByCategory() + - noSameRecommendMenu() + - addRecommendMenu() + +- CoachList + - getAllCoaches() + +- DayCategory + - getCategory() + +- DayCategoryList + - isMaxCount() + - addCategory() + +- HateMenuList + - getMenus() + +- Menu + +- MenuMap + - getInstance() + - contains() + - getExceptHateMenus() + +- RecommendResult + +## 열거형 목록 +- ProgressMessage +- ErrorMessage +- Category +- Day +- ResultTag diff --git a/src/main/java/menu/Application.java b/src/main/java/menu/Application.java index 6340b6f33..53f559e86 100644 --- a/src/main/java/menu/Application.java +++ b/src/main/java/menu/Application.java @@ -1,7 +1,10 @@ package menu; +import menu.factory.ComponentFactory; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + final ComponentFactory componentFactory = new ComponentFactory(); + componentFactory.menuController().start(); } } diff --git a/src/main/java/menu/constant/Category.java b/src/main/java/menu/constant/Category.java new file mode 100644 index 000000000..4468fa42d --- /dev/null +++ b/src/main/java/menu/constant/Category.java @@ -0,0 +1,31 @@ +package menu.constant; + +import java.util.Arrays; + +public enum Category { + JAPANESE_FOOD(1, "일식"), + KOREAN_FOOD(2, "한식"), + CHINESE_FOOD(3, "중식"), + ASIAN_FOOD(4, "아시안"), + WESTERN_FOOD(5, "양식"); + + private final int index; + private final String toKorean; + + Category(final int index, final String toKorean) { + this.index = index; + this.toKorean = toKorean; + } + + public static Category getByIndex(final int index) { + return Arrays.stream(values()) + .filter(c -> c.index == index) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_MATCHING_INDEX.toString())); + } + + @Override + public String toString() { + return this.toKorean; + } +} diff --git a/src/main/java/menu/constant/ErrorMessage.java b/src/main/java/menu/constant/ErrorMessage.java new file mode 100644 index 000000000..6b7826961 --- /dev/null +++ b/src/main/java/menu/constant/ErrorMessage.java @@ -0,0 +1,23 @@ +package menu.constant; + +public enum ErrorMessage { + INVALID_DELIMITER_USAGE("구분자의 사용이 잘못되었습니다."), + INVALID_COACH_NUM_ERROR("코치 수가 잘못되었습니다."), + INVALID_COACH_NAME_LENGTH("코치의 이름 길이가 잘못되었습니다."), + INVALID_MENU_NAME("잘못된 메뉴명입니다."), + NO_MATCHING_INDEX("인덱스에 해당하는 값이 없습니다."), + CANNOT_FIND_CATEGORY("카테고리를 찾을 수 없습니다."), + INVALID_HATE_MENU_NUM("잘못된 못먹는 음식 개수입니다."); + + private static final String PREFIX = "[ERROR] "; + private final String message; + + ErrorMessage(final String message) { + this.message = message; + } + + @Override + public String toString() { + return PREFIX + this.message; + } +} diff --git a/src/main/java/menu/constant/ProgressMessage.java b/src/main/java/menu/constant/ProgressMessage.java new file mode 100644 index 000000000..e44f19c92 --- /dev/null +++ b/src/main/java/menu/constant/ProgressMessage.java @@ -0,0 +1,21 @@ +package menu.constant; + +public enum ProgressMessage { + START_MESSAGE("점심 메뉴 추천을 시작합니다."), + COACH_NAME_REQUEST("코치의 이름을 입력해 주세요. (, 로 구분)"), + COACH_MENU_REQUEST("%s(이)가 못 먹는 메뉴를 입력해 주세요."), + RECOMMEND_RESULT("메뉴 추천 결과입니다."), + DAY_CLASSIFIER("[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ]"), + RECOMMEND_FINISH("\n추천을 완료했습니다."); + + private final String message; + + ProgressMessage(final String message) { + this.message = message; + } + + @Override + public String toString() { + return this.message; + } +} diff --git a/src/main/java/menu/constant/ResultTag.java b/src/main/java/menu/constant/ResultTag.java new file mode 100644 index 000000000..0ffce5c43 --- /dev/null +++ b/src/main/java/menu/constant/ResultTag.java @@ -0,0 +1,18 @@ +package menu.constant; + +public enum ResultTag { + START_TAG("[ "), + DELIMITER(" | "), + END_TAG(" ]"); + + private final String tag; + + ResultTag(final String tag) { + this.tag = tag; + } + + @Override + public String toString() { + return this.tag; + } +} diff --git a/src/main/java/menu/controller/MenuController.java b/src/main/java/menu/controller/MenuController.java new file mode 100644 index 000000000..b54f514b1 --- /dev/null +++ b/src/main/java/menu/controller/MenuController.java @@ -0,0 +1,44 @@ +package menu.controller; + +import menu.domain.CoachList; +import menu.domain.RecommendResult; +import menu.io.InputManager; +import menu.io.OutputView; +import menu.service.MenuService; + +public class MenuController { + private final OutputView outputView; + private final InputManager inputManager; + private final MenuService menuService; + + public MenuController(final OutputView outputView, final InputManager inputManager, final MenuService menuService) { + this.outputView = outputView; + this.inputManager = inputManager; + this.menuService = menuService; + } + + public void start() { + outputView.printStart(); + final CoachList coachList = createCoachList(); + createCoachMenuMap(coachList); + recommendMenu(); + } + + private void recommendMenu() { + final RecommendResult recommendResult = menuService.recommendMenu(); + outputView.printRecommendResult(recommendResult); + } + + private void createCoachMenuMap(final CoachList coachList) { + coachList.getAllCoaches().forEach(coach -> { + outputView.printCoachMenuRequest(coach); + menuService.addHateMenus(coach, inputManager.readCoachMenu()); + }); + } + + private CoachList createCoachList() { + outputView.printCoachNameRequest(); + final CoachList coachList = inputManager.readCoach(); + return menuService.saveCoachList(coachList); + } +} diff --git a/src/main/java/menu/domain/AvailMenuList.java b/src/main/java/menu/domain/AvailMenuList.java new file mode 100644 index 000000000..82a32f27a --- /dev/null +++ b/src/main/java/menu/domain/AvailMenuList.java @@ -0,0 +1,18 @@ +package menu.domain; + +import menu.constant.Category; + +import java.util.List; +import java.util.Map; + +public class AvailMenuList { + private final Map> coachAvailMenu; + + public AvailMenuList(final Map> coachAvailMenu) { + this.coachAvailMenu = coachAvailMenu; + } + + public List getMenuByCategory(final Category category) { + return this.coachAvailMenu.get(category); + } +} diff --git a/src/main/java/menu/domain/Coach.java b/src/main/java/menu/domain/Coach.java new file mode 100644 index 000000000..b22ff568d --- /dev/null +++ b/src/main/java/menu/domain/Coach.java @@ -0,0 +1,71 @@ +package menu.domain; + +import menu.constant.Category; +import menu.constant.ErrorMessage; +import menu.constant.ResultTag; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class Coach { + private static final int MIN_NAME_LENGTH = 2; + private static final int MAX_NAME_LENGTH = 4; + private final String name; + private final List recommendMenuList; + private HateMenuList hateMenuList; + private AvailMenuList availMenuList; + + public Coach(final String name) { + validateName(name); + this.name = name; + recommendMenuList = new ArrayList<>(); + } + + private void validateName(final String name) { + if (invalidLength(name.length())) { + throw new IllegalArgumentException(ErrorMessage.INVALID_COACH_NAME_LENGTH.toString()); + } + } + + private boolean invalidLength(final int length) { + return length < MIN_NAME_LENGTH || length > MAX_NAME_LENGTH; + } + + @Override + public String toString() { + return ResultTag.START_TAG + + name + + ResultTag.DELIMITER + + recommendMenuList.stream().map(Menu::toString).collect(Collectors.joining(ResultTag.DELIMITER.toString())) + + ResultTag.END_TAG; + } + + public String getName() { + return this.name; + } + + public void addHateMenus(final HateMenuList hateMenuList) { + this.hateMenuList = hateMenuList; + this.availMenuList = new AvailMenuList(getAvailMenu()); + } + + private Map> getAvailMenu() { + return MenuMap.getInstance().getExceptHateMenus(hateMenuList.getMenus()); + } + + public List getAvailMenuByCategory(final Category category) { + return this.availMenuList.getMenuByCategory(category).stream() + .map(Menu::toString) + .collect(Collectors.toList()); + } + + public boolean noSameRecommendMenu(final String menu) { + return recommendMenuList.stream().noneMatch(m -> m.toString().equals(menu)); + } + + public void addRecommendMenu(final Menu menu) { + this.recommendMenuList.add(menu); + } +} diff --git a/src/main/java/menu/domain/CoachList.java b/src/main/java/menu/domain/CoachList.java new file mode 100644 index 000000000..7133017b3 --- /dev/null +++ b/src/main/java/menu/domain/CoachList.java @@ -0,0 +1,31 @@ +package menu.domain; + +import menu.constant.ErrorMessage; + +import java.util.List; +import java.util.stream.Collectors; + +public class CoachList { + private static final int MIN_COACH_NUM = 2; + private final List coaches; + + public CoachList(final List coaches) { + validateSize(coaches); + this.coaches = coaches.stream().map(Coach::new).collect(Collectors.toList()); + } + + private void validateSize(final List coaches) { + if (coaches.size() < MIN_COACH_NUM) { + throw new IllegalArgumentException(ErrorMessage.INVALID_COACH_NUM_ERROR.toString()); + } + } + + public List getAllCoaches() { + return this.coaches; + } + + @Override + public String toString() { + return coaches.stream().map(Coach::toString).collect(Collectors.joining("\n")); + } +} diff --git a/src/main/java/menu/domain/DayCategory.java b/src/main/java/menu/domain/DayCategory.java new file mode 100644 index 000000000..207f781bc --- /dev/null +++ b/src/main/java/menu/domain/DayCategory.java @@ -0,0 +1,20 @@ +package menu.domain; + +import menu.constant.Category; + +public class DayCategory { + private final Category category; + + public DayCategory(final Category category) { + this.category = category; + } + + @Override + public String toString() { + return this.category.toString(); + } + + public Category getCategory() { + return this.category; + } +} diff --git a/src/main/java/menu/domain/DayCategoryList.java b/src/main/java/menu/domain/DayCategoryList.java new file mode 100644 index 000000000..999bb92da --- /dev/null +++ b/src/main/java/menu/domain/DayCategoryList.java @@ -0,0 +1,34 @@ +package menu.domain; + +import menu.constant.Category; +import menu.constant.ResultTag; + +import java.util.*; +import java.util.stream.Collectors; + +public class DayCategoryList { + private static final String CATEGORY_TAG = "카테고리"; + private final List dayCategoryList; + + public DayCategoryList() { + this.dayCategoryList = new ArrayList<>(); + } + + public boolean isMaxCount(final Category category) { + return dayCategoryList.stream().filter(d -> d.getCategory() == category).count() == 2; + } + + public void addCategory(final Category category) { + final DayCategory dayCategory = new DayCategory(category); + dayCategoryList.add(dayCategory); + } + + @Override + public String toString() { + return ResultTag.START_TAG + + CATEGORY_TAG + + ResultTag.DELIMITER + + dayCategoryList.stream().map(DayCategory::toString).collect(Collectors.joining(ResultTag.DELIMITER.toString())) + + ResultTag.END_TAG; + } +} diff --git a/src/main/java/menu/domain/HateMenuList.java b/src/main/java/menu/domain/HateMenuList.java new file mode 100644 index 000000000..70462003a --- /dev/null +++ b/src/main/java/menu/domain/HateMenuList.java @@ -0,0 +1,26 @@ +package menu.domain; + +import menu.constant.ErrorMessage; + +import java.util.List; +import java.util.stream.Collectors; + +public class HateMenuList { + private static final int MAX_SIZE = 2; + private final List menus; + + public HateMenuList(final List menus) { + validateSize(menus); + this.menus = menus.stream().map(Menu::new).collect(Collectors.toList()); + } + + private void validateSize(final List menus) { + if (menus.size() > MAX_SIZE) { + throw new IllegalArgumentException(ErrorMessage.INVALID_HATE_MENU_NUM.toString()); + } + } + + public List getMenus() { + return this.menus; + } +} diff --git a/src/main/java/menu/domain/Menu.java b/src/main/java/menu/domain/Menu.java new file mode 100644 index 000000000..056ee02d5 --- /dev/null +++ b/src/main/java/menu/domain/Menu.java @@ -0,0 +1,27 @@ +package menu.domain; + +import menu.constant.ErrorMessage; + +public final class Menu { + private final String name; + + public Menu(final String name) { + validateName(name); + this.name = name; + } + + private void validateName(final String name) { + if (!name.isEmpty() && notContainsName(name)) { + throw new IllegalArgumentException(ErrorMessage.INVALID_MENU_NAME.toString()); + } + } + + private boolean notContainsName(final String name) { + return !MenuMap.getInstance().contains(name); + } + + @Override + public String toString() { + return this.name; + } +} diff --git a/src/main/java/menu/domain/MenuMap.java b/src/main/java/menu/domain/MenuMap.java new file mode 100644 index 000000000..5dbac5865 --- /dev/null +++ b/src/main/java/menu/domain/MenuMap.java @@ -0,0 +1,62 @@ +package menu.domain; + +import menu.constant.Category; +import menu.constant.ErrorMessage; + +import java.util.*; +import java.util.stream.Collectors; + +public class MenuMap { + private final Map> menuMap; + private static MenuMap instance; + + private MenuMap() { + menuMap = new HashMap<>(); + initializeMap(); + } + + public static MenuMap getInstance() { + if (instance == null) { + instance = new MenuMap(); + } + return instance; + } + + public boolean contains(final String name) { + return this.menuMap.values().stream() + .flatMap(Collection::stream) + .anyMatch(name::equals); + } + + private Category getCategoryByMenu(final Menu menu) { + return menuMap.entrySet().stream() + .filter(entry -> entry.getValue().contains(menu.toString())) + .findAny() + .map(Map.Entry::getKey) + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.CANNOT_FIND_CATEGORY.toString())); + } + + public Map> getExceptHateMenus(final List hateMenus) { + final List hateMenuString = getHateMenuString(hateMenus); + + return this.menuMap.values().stream() + .flatMap(Collection::stream) + .filter(menu -> !hateMenuString.contains(menu)) + .map(Menu::new) + .collect(Collectors.groupingBy(this::getCategoryByMenu)); + } + + private List getHateMenuString(final List hateMenus) { + return hateMenus.stream() + .map(Menu::toString) + .collect(Collectors.toList()); + } + + private void initializeMap() { + menuMap.put(Category.JAPANESE_FOOD, List.of("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼")); + menuMap.put(Category.KOREAN_FOOD, List.of("김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음")); + menuMap.put(Category.CHINESE_FOOD, List.of("깐풍기", "볶음면", "동파육", "짜장면", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채")); + menuMap.put(Category.ASIAN_FOOD, List.of("팟타이", "카오 팟", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜")); + menuMap.put(Category.WESTERN_FOOD, List.of("라자냐", "그라탱", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "스파게티", "피자", "파니니")); + } +} diff --git a/src/main/java/menu/domain/RecommendResult.java b/src/main/java/menu/domain/RecommendResult.java new file mode 100644 index 000000000..db4bd45f4 --- /dev/null +++ b/src/main/java/menu/domain/RecommendResult.java @@ -0,0 +1,19 @@ +package menu.domain; + +public class RecommendResult { + private static final String LINE_DELIMITER = "\n"; + private final CoachList coachList; + private final DayCategoryList dayCategoryList; + + public RecommendResult(final CoachList coachList, final DayCategoryList dayCategoryList) { + this.coachList = coachList; + this.dayCategoryList = dayCategoryList; + } + + @Override + public String toString() { + return dayCategoryList + + LINE_DELIMITER + + coachList; + } +} diff --git a/src/main/java/menu/factory/ComponentFactory.java b/src/main/java/menu/factory/ComponentFactory.java new file mode 100644 index 000000000..2fcfc3aff --- /dev/null +++ b/src/main/java/menu/factory/ComponentFactory.java @@ -0,0 +1,45 @@ +package menu.factory; + +import menu.controller.MenuController; +import menu.io.InputManager; +import menu.io.InputValidator; +import menu.io.InputView; +import menu.io.OutputView; +import menu.repository.MenuRepository; +import menu.service.MenuService; +import menu.utils.MenuGenerator; + +public class ComponentFactory { + + public MenuController menuController() { + return new MenuController(outputView(), inputManager(), menuService()); + } + + private MenuService menuService() { + return new MenuService(menuRepository(), menuGenerator()); + } + + private MenuGenerator menuGenerator() { + return new MenuGenerator(); + } + + private MenuRepository menuRepository() { + return new MenuRepository(); + } + + private InputManager inputManager() { + return new InputManager(inputView()); + } + + private InputView inputView() { + return new InputView(inputValidator()); + } + + private InputValidator inputValidator() { + return new InputValidator(); + } + + private OutputView outputView() { + return new OutputView(); + } +} diff --git a/src/main/java/menu/io/InputManager.java b/src/main/java/menu/io/InputManager.java new file mode 100644 index 000000000..1f6cda16c --- /dev/null +++ b/src/main/java/menu/io/InputManager.java @@ -0,0 +1,34 @@ +package menu.io; + +import menu.domain.CoachList; +import menu.domain.HateMenuList; + +import java.util.Arrays; +import java.util.function.Supplier; + +public class InputManager { + private static final String SPLITTER = ","; + private final InputView inputView; + + public InputManager(final InputView inputView) { + this.inputView = inputView; + } + + public CoachList readCoach() { + return read(() -> new CoachList(Arrays.asList(inputView.readCoach().split(SPLITTER)))); + } + + public HateMenuList readCoachMenu() { + return read(() -> new HateMenuList(Arrays.asList(inputView.readCoachMenu().split(SPLITTER)))); + } + + private T read(final Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (final IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } +} diff --git a/src/main/java/menu/io/InputValidator.java b/src/main/java/menu/io/InputValidator.java new file mode 100644 index 000000000..487486067 --- /dev/null +++ b/src/main/java/menu/io/InputValidator.java @@ -0,0 +1,24 @@ +package menu.io; + +import menu.constant.ErrorMessage; + +public class InputValidator { + private static final String DELIMETER = ","; + + public void validateCoachMenu(final String input) { + if (input.isEmpty()) { + return; + } + validateSplitter(input); + } + + public void validateSplitter(final String input) { + if (invalidSplitterUsage(input)) { + throw new IllegalArgumentException(ErrorMessage.INVALID_DELIMITER_USAGE.toString()); + } + } + + private boolean invalidSplitterUsage(final String input) { + return input.startsWith(DELIMETER) || input.endsWith(DELIMETER); + } +} diff --git a/src/main/java/menu/io/InputView.java b/src/main/java/menu/io/InputView.java new file mode 100644 index 000000000..0a626ae3b --- /dev/null +++ b/src/main/java/menu/io/InputView.java @@ -0,0 +1,23 @@ +package menu.io; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + private final InputValidator inputValidator; + + public InputView(final InputValidator inputValidator) { + this.inputValidator = inputValidator; + } + + public String readCoach() { + final String input = Console.readLine(); + inputValidator.validateSplitter(input); + return input; + } + + public String readCoachMenu() { + final String input = Console.readLine(); + inputValidator.validateCoachMenu(input); + return input; + } +} diff --git a/src/main/java/menu/io/OutputView.java b/src/main/java/menu/io/OutputView.java new file mode 100644 index 000000000..41a752a18 --- /dev/null +++ b/src/main/java/menu/io/OutputView.java @@ -0,0 +1,27 @@ +package menu.io; + +import menu.constant.ProgressMessage; +import menu.domain.Coach; +import menu.domain.RecommendResult; + +public class OutputView { + + public void printStart() { + System.out.println(ProgressMessage.START_MESSAGE); + } + + public void printCoachNameRequest() { + System.out.println(ProgressMessage.COACH_NAME_REQUEST); + } + + public void printCoachMenuRequest(final Coach coach) { + System.out.println(String.format(ProgressMessage.COACH_MENU_REQUEST.toString(), coach.getName())); + } + + public void printRecommendResult(final RecommendResult recommendResult) { + System.out.println(ProgressMessage.RECOMMEND_RESULT); + System.out.println(ProgressMessage.DAY_CLASSIFIER); + System.out.println(recommendResult); + System.out.println(ProgressMessage.RECOMMEND_FINISH); + } +} diff --git a/src/main/java/menu/repository/MenuRepository.java b/src/main/java/menu/repository/MenuRepository.java new file mode 100644 index 000000000..12c4c2737 --- /dev/null +++ b/src/main/java/menu/repository/MenuRepository.java @@ -0,0 +1,16 @@ +package menu.repository; + +import menu.domain.CoachList; + +public class MenuRepository { + private CoachList coachList; + + public CoachList saveCoachList(final CoachList coachList) { + this.coachList = coachList; + return this.coachList; + } + + public CoachList findCoachList() { + return this.coachList; + } +} diff --git a/src/main/java/menu/service/MenuService.java b/src/main/java/menu/service/MenuService.java new file mode 100644 index 000000000..74e5a7cd1 --- /dev/null +++ b/src/main/java/menu/service/MenuService.java @@ -0,0 +1,30 @@ +package menu.service; + +import menu.domain.*; +import menu.domain.RecommendResult; +import menu.repository.MenuRepository; +import menu.utils.MenuGenerator; + +public class MenuService { + private final MenuRepository menuRepository; + private final MenuGenerator menuGenerator; + + public MenuService(final MenuRepository menuRepository, final MenuGenerator menuGenerator) { + this.menuRepository = menuRepository; + this.menuGenerator = menuGenerator; + } + + public RecommendResult recommendMenu() { + final CoachList coachList = menuRepository.findCoachList(); + final DayCategoryList dayCategoryList = menuGenerator.generate(coachList); + return new RecommendResult(coachList, dayCategoryList); + } + + public CoachList saveCoachList(final CoachList coachList) { + return menuRepository.saveCoachList(coachList); + } + + public void addHateMenus(final Coach coach, final HateMenuList hateMenuList) { + coach.addHateMenus(hateMenuList); + } +} diff --git a/src/main/java/menu/utils/MenuGenerator.java b/src/main/java/menu/utils/MenuGenerator.java new file mode 100644 index 000000000..e5513abaa --- /dev/null +++ b/src/main/java/menu/utils/MenuGenerator.java @@ -0,0 +1,50 @@ +package menu.utils; + +import camp.nextstep.edu.missionutils.Randoms; +import menu.domain.*; +import menu.constant.Category; + +import java.util.List; +import java.util.stream.IntStream; + +public class MenuGenerator { + private static final int MIN_INDEX = 0; + private static final int MAX_INDEX = 5; + private static final int MIN_RANGE = 1; + private static final int MAX_RANGE = 5; + + public DayCategoryList generate(final CoachList coachList) { + final DayCategoryList dayCategoryList = new DayCategoryList(); + IntStream.range(MIN_INDEX, MAX_INDEX).forEach(i -> addOneDayRecommend(dayCategoryList, coachList)); + return dayCategoryList; + } + + private void addOneDayRecommend(final DayCategoryList dayCategoryList, final CoachList coachList) { + final Category category = generateCategory(dayCategoryList); + + dayCategoryList.addCategory(category); + coachList.getAllCoaches().forEach(coach -> recommendMenu(coach, category)); + } + + private Menu recommendMenu(final Coach coach, final Category category) { + final List menus = coach.getAvailMenuByCategory(category); + while (true) { + final String menu = Randoms.shuffle(menus).get(0); + if (coach.noSameRecommendMenu(menu)) { + final Menu recommendMenu = new Menu(menu); + coach.addRecommendMenu(recommendMenu); + return recommendMenu; + } + } + } + + private Category generateCategory(final DayCategoryList dayCategoryList) { + while (true) { + final Category category = Category.getByIndex(Randoms.pickNumberInRange(MIN_RANGE, MAX_RANGE)); + if (dayCategoryList.isMaxCount(category)) { + continue; + } + return category; + } + } +} diff --git a/src/test/java/menu/ApplicationTest.java b/src/test/java/menu/ApplicationTest.java index a757e7813..4a1190d59 100644 --- a/src/test/java/menu/ApplicationTest.java +++ b/src/test/java/menu/ApplicationTest.java @@ -71,6 +71,49 @@ class AllFeatureTest { ); }); } + + @Test + void 커스텀_기능_테스트() { + assertTimeoutPreemptively(RANDOM_TEST_TIMEOUT, () -> { + final Executable executable = () -> { + runException("구구,제임스", "김밥", "떡볶이"); + + assertThat(output()).contains( + "점심 메뉴 추천을 시작합니다.", + "코치의 이름을 입력해 주세요. (, 로 구분)", + "구구(이)가 못 먹는 메뉴를 입력해 주세요.", + "제임스(이)가 못 먹는 메뉴를 입력해 주세요.", + "메뉴 추천 결과입니다.", + "[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ]", + "추천을 완료했습니다." + ); + }; + assertRandomTest(executable, + Mocking.ofRandomNumberInRange(2, 2, 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("파인애플 볶음밥", "팟타이", "카오 팟", "나시고렝", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜") // 제임스 + ) + ); + }); + } } @Override diff --git a/src/test/java/menu/domain/CoachListTest.java b/src/test/java/menu/domain/CoachListTest.java new file mode 100644 index 000000000..06e20c25e --- /dev/null +++ b/src/test/java/menu/domain/CoachListTest.java @@ -0,0 +1,7 @@ +package menu.domain; + +import static org.junit.jupiter.api.Assertions.*; + +class CoachListTest { + +} \ No newline at end of file diff --git a/src/test/java/menu/domain/CoachTest.java b/src/test/java/menu/domain/CoachTest.java new file mode 100644 index 000000000..22d7e6aa9 --- /dev/null +++ b/src/test/java/menu/domain/CoachTest.java @@ -0,0 +1,7 @@ +package menu.domain; + +import static org.junit.jupiter.api.Assertions.*; + +class CoachTest { + +} \ No newline at end of file diff --git a/src/test/java/menu/domain/DomainValidationTest.java b/src/test/java/menu/domain/DomainValidationTest.java new file mode 100644 index 000000000..cdd54465c --- /dev/null +++ b/src/test/java/menu/domain/DomainValidationTest.java @@ -0,0 +1,47 @@ +package menu.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DisplayName("도메인 생성시 검증 로직 중") +class DomainValidationTest { + + @Test + @DisplayName("코치의 이름의 길이가 2 미만인 경우 예외를 던진다.") + void coachValidation1() { + assertThatThrownBy(() -> new Coach("a")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("코치의 이름의 길이가 4 초과인 경우 예외를 던진다.") + void coachValidation2() { + assertThatThrownBy(() -> new Coach("aaaaa")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("코치가 2명 미만인 경우 예외를 던진다.") + void coachListValidation() { + assertThatThrownBy(() -> new CoachList(List.of("aa"))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("코치의 이름의 길이가 4 초과인 경우 예외를 던진다.") + void hateMenuListValidation() { + assertThatThrownBy(() -> new HateMenuList(List.of("aa", "bb", "cc"))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("정해진 이름의 메뉴가 아닌 경우 예외를 던진다.") + void menuValidation() { + assertThatThrownBy(() -> new Menu("aa")) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/menu/domain/HateMenuListTest.java b/src/test/java/menu/domain/HateMenuListTest.java new file mode 100644 index 000000000..e30906b54 --- /dev/null +++ b/src/test/java/menu/domain/HateMenuListTest.java @@ -0,0 +1,7 @@ +package menu.domain; + +import static org.junit.jupiter.api.Assertions.*; + +class HateMenuListTest { + +} \ No newline at end of file diff --git a/src/test/java/menu/domain/MenuTest.java b/src/test/java/menu/domain/MenuTest.java new file mode 100644 index 000000000..b9a2e20a5 --- /dev/null +++ b/src/test/java/menu/domain/MenuTest.java @@ -0,0 +1,7 @@ +package menu.domain; + +import static org.junit.jupiter.api.Assertions.*; + +class MenuTest { + +} \ No newline at end of file diff --git a/src/test/java/menu/io/InputValidatorTest.java b/src/test/java/menu/io/InputValidatorTest.java new file mode 100644 index 000000000..876cbddec --- /dev/null +++ b/src/test/java/menu/io/InputValidatorTest.java @@ -0,0 +1,25 @@ +package menu.io; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DisplayName("InputValidator 의 검증로직에서") +class InputValidatorTest { + private final InputValidator inputValidator = new InputValidator(); + + @Test + @DisplayName("콤마로 끝나는 입력값에 대해 예외를 던진다.") + void validateSplitter1() { + assertThatThrownBy(() -> inputValidator.validateCoachMenu("aa,")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("콤마로 시작하는 입력값에 대해 예외를 던진다.") + void validateSplitter2() { + assertThatThrownBy(() -> inputValidator.validateCoachMenu(",aa")) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/menu/service/MenuServiceTest.java b/src/test/java/menu/service/MenuServiceTest.java new file mode 100644 index 000000000..be4d12e3f --- /dev/null +++ b/src/test/java/menu/service/MenuServiceTest.java @@ -0,0 +1,59 @@ +package menu.service; + +import menu.domain.*; +import menu.repository.MenuRepository; +import menu.utils.MenuGenerator; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("MenuService의") +class MenuServiceTest { + private final MenuRepository menuRepository = new MenuRepository(); + private final MenuService menuService = new MenuService(menuRepository, new MenuGenerator()); + + @Test + @DisplayName("메뉴 추천이 수행되는가") + void recommendMenu() { + //given + final CoachList coachList = menuRepository.saveCoachList(new CoachList(List.of("aa", "bb"))); + final String 짜장면 = "짜장면"; + coachList.getAllCoaches().forEach(c -> c.addHateMenus(new HateMenuList(List.of(짜장면)))); + + //when + final RecommendResult recommendResult = menuService.recommendMenu(); + + //then + assertThat(recommendResult.toString()).doesNotContain(짜장면); + } + + @Test + @DisplayName("코치 리스트 저장이 수행되는가") + void saveCoachList() { + //given + + //when + final CoachList expected = new CoachList(List.of("aa", "bb")); + final CoachList coachList = menuService.saveCoachList(expected); + + //then + assertThat(coachList).isEqualTo(expected); + } + + @Test + @DisplayName("못먹는 음식 추가가 수행되는가") + void addHateMenus() { + //given + menuRepository.saveCoachList(new CoachList(List.of("aa", "bb"))); + + //when + final Coach coach = menuRepository.findCoachList().getAllCoaches().get(0); + menuService.addHateMenus(coach, new HateMenuList(List.of("탕수육", "짜장면"))); + + //then + + } +} diff --git a/src/test/java/menu/utils/MenuGeneratorTest.java b/src/test/java/menu/utils/MenuGeneratorTest.java new file mode 100644 index 000000000..79e87f7a2 --- /dev/null +++ b/src/test/java/menu/utils/MenuGeneratorTest.java @@ -0,0 +1,31 @@ +package menu.utils; + +import menu.domain.CoachList; +import menu.domain.DayCategoryList; +import menu.domain.HateMenuList; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +@DisplayName("MenuGenerator의") +class MenuGeneratorTest { + private final MenuGenerator menuGenerator = new MenuGenerator(); + + @Test + @DisplayName("메뉴 생성 로직이 수행되는가") + void generate() { + //given + final CoachList coachList = new CoachList(List.of("aa", "bb")); + final String 탕수육 = "탕수육"; + coachList.getAllCoaches().forEach(c -> c.addHateMenus(new HateMenuList(List.of(탕수육)))); + + //when + final DayCategoryList dayCategoryList = menuGenerator.generate(coachList); + + //then + assertThat(dayCategoryList.toString()).doesNotContain(탕수육); + } +}