Skip to content

6기 실전 연습 #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7f3736c
docs: 기능 목록 정리
SeonJuuuun Sep 12, 2023
33ed3b7
feat: 메뉴 추천을 받을 코치의 이름을 입력받는다.
SeonJuuuun Sep 12, 2023
02ee06a
feat: 코치의 이름은 빈 값일 수 없다.
SeonJuuuun Sep 12, 2023
ae594f1
feat: 코치의 이름은 최소 2글자, 최대 4글자이다.
SeonJuuuun Sep 12, 2023
84d7e1a
feat: 코치는 최소 2명, 최대 5명까지 식사를 함께한다.
SeonJuuuun Sep 12, 2023
fdcb515
feat: 코치의 이름은 중복될 수 없다.
SeonJuuuun Sep 12, 2023
899d1d7
feat: 객체 비교를 위한 equals, hashCode 재정의
SeonJuuuun Sep 12, 2023
bb15c6c
feat: 랜덤값과 음식종류를 나타내는 Category 생성
SeonJuuuun Sep 12, 2023
6a00c1d
feat: 카테고리와 메뉴를 초기화한다.
SeonJuuuun Sep 12, 2023
db53b0b
feat: 각 코치가 못 먹는 메뉴를 입력받는다.
SeonJuuuun Sep 12, 2023
ada3619
refactor: 못 먹는 메뉴가 있으면 빈 값 입력 가능으로 인한 리팩터링
SeonJuuuun Sep 12, 2023
4118e7e
feat: 각 요일에 추천할 카테고리를 무작위로 정한다.
SeonJuuuun Sep 13, 2023
76a5871
feat: 컨트롤러에 맵을 넣어준다.
SeonJuuuun Sep 13, 2023
a3fb630
feat: 요일 출력을 위한 Date 클래스 생성
SeonJuuuun Sep 14, 2023
f971da5
feat: 추천 메뉴를 선정하기 위한 메서드 생성
SeonJuuuun Sep 14, 2023
c39255f
feat: 값을 제대로 가져오기 위한 toString 재정의
SeonJuuuun Sep 14, 2023
eba78bd
feat: 먹을 수 있는 메뉴를 저장하기 위한 메서드 구현
SeonJuuuun Sep 14, 2023
0153122
refactor: 초기 값 초기화 리팩터링
SeonJuuuun Sep 14, 2023
2947c29
refactor: 올바르지 않은 값을 입력할 때 다시 시작하게 해주는 메서드 구현 및 못 먹는 메뉴 입력 리팩터링
SeonJuuuun Sep 14, 2023
c9aac70
refactor: 불 필요한 클래스 삭제
SeonJuuuun Sep 14, 2023
78120ef
refactor: 전체 출력문 구현
SeonJuuuun Sep 14, 2023
138dccb
feat: 요일 별 카테고리 고르는 메서드 구현
SeonJuuuun Sep 14, 2023
2b63914
refactor: 불 필요한 공백이나 띄어쓰기 삭제
SeonJuuuun Sep 14, 2023
d8afe91
feat: 기능 목록 정리에 있는 기능 구현
SeonJuuuun Sep 14, 2023
9e26daf
feat: 각 코치가 못 먹는 메뉴를 입력할 때 예외 처리 구현
SeonJuuuun Sep 14, 2023
82e958f
feat: equals, hashCode 재 정의 및 각 메뉴는 중복될 수 없는 예외 처리 구현
SeonJuuuun Sep 14, 2023
c491080
refactor: [ERROR] 문구 추가
SeonJuuuun Sep 14, 2023
84eb7b0
refactor: 불 필요한 클래스 삭제
SeonJuuuun Sep 15, 2023
516fe0e
refactor: 예외 처리를 모아놓은 클래스 생성
SeonJuuuun Sep 15, 2023
6839720
refactor: 불 필요한 메서드 삭제
SeonJuuuun Sep 15, 2023
b9d7c80
refactor: CoachesTest 삭제
SeonJuuuun Sep 15, 2023
e579149
refactor: 불 필요한 import 삭제
SeonJuuuun Sep 15, 2023
3120c7d
test: Coach에 대한 test 구현
SeonJuuuun Sep 15, 2023
59326a6
test: InputValidator에 대한 테스트 구현
SeonJuuuun Sep 15, 2023
bb17a9d
test: Menu에 대한 테스트 구현
SeonJuuuun Sep 15, 2023
99d287f
test: Category에 대한 테스트 구현
SeonJuuuun Sep 15, 2023
de57b3d
feat: 월~금의 값을 가져오는 메서드 생성
SeonJuuuun Sep 16, 2023
6b57687
refactor: 추천 기능들 Recommender 객체로 이동
SeonJuuuun Sep 16, 2023
1b857c6
refactor: 불 필요한 클래스 삭제
SeonJuuuun Sep 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### 기능 목록 정리

- [x] 메뉴 추천을 받을 코치의 이름을 입력받는다.
- [x] 코치의 이름은 최소 2글자, 최대 4글자이다.
- [x] 코치는 최소 2명, 최대 5명까지 식사를 함께한다.
- [x] 코치의 이름은 중복될 수 없다.
- [x] 코치의 이름은 빈 값일 수 없다.
- [x] 카테고리와 메뉴를 초기화한다.
- [x] 각 코치가 못 먹는 메뉴를 입력받는다.
- [x] 각 코치는 최소 0개, 최대 2개의 못 먹는 메뉴가 있다.
- [x] 먹지 못하는 메뉴가 없으면 빈 값을 입력한다.
- [x] 존재하지 않는 메뉴 입력시 에러처리한다.
- [x] 각 메뉴는 중복될 수 없다.
- [x] 메뉴를 추천해준다.
- [x] 각 요일에 추천할 카테고리를 무작위로 정한다.
- [x] 각 코치가 해당 요일에 먹을 메뉴를 추천한다.
- [x] 월~금에 대해 해당 내용을 반복한다.
- [x] 한 주에 같은 카테고리는 최대 2회까지만 고를 수 있다.
- [x] 각 코치에게 한 주에 중복되지 않는 메뉴를 추천해야한다.
- [x] 각 코치가 먹지 못하는 메뉴는 추천해주지 않는다.
- [x] 추천 메뉴를 출력한다.
7 changes: 7 additions & 0 deletions src/main/java/menu/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package menu;

import menu.controller.MenuController;
import menu.domain.Recommender;

public class Application {

public static void main(String[] args) {
// TODO: 프로그램 구현
MenuController menuController = new MenuController(InitialMenu.initMap(),
new Recommender());
menuController.start();
}
}
23 changes: 23 additions & 0 deletions src/main/java/menu/InitialMenu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package menu;

import static menu.domain.Category.*;

import java.util.HashMap;
import java.util.Map;
import menu.domain.Category;

public class InitialMenu {

private InitialMenu() {
}

public static Map<Category, String> initMap() {
Map<Category, String> menuMap = new HashMap<>();
menuMap.put(JAPAN, "규동, 우동, 미소시루, 스시, 가츠동, 오니기리, 하이라이스, 라멘, 오코노미야끼");
menuMap.put(KOREAN, "김밥, 김치찌개, 쌈밥, 된장찌개, 비빔밥, 칼국수, 불고기, 떡볶이, 제육볶음");
menuMap.put(CHINA, "깐풍기, 볶음면, 동파육, 짜장면, 짬뽕, 마파두부, 탕수육, 토마토 달걀볶음, 고추잡채");
menuMap.put(ASIAN, "팟타이, 카오 팟, 나시고렝, 파인애플 볶음밥, 쌀국수, 똠얌꿍, 반미, 월남쌈, 분짜");
menuMap.put(WESTERN, "라자냐, 그라탱, 뇨끼, 끼슈, 프렌치 토스트, 바게트, 스파게티, 피자, 파니니");
Comment on lines +14 to +20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메뉴을 하나의 String 이 아닌 여러 String으로 나누어서 넣어보면 어떨까요?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그런 방법도 있겠네요! 감사합니다😄

return menuMap;
}
}
86 changes: 86 additions & 0 deletions src/main/java/menu/controller/MenuController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package menu.controller;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import menu.domain.Category;
import menu.domain.Coach;
import menu.domain.Date;
import menu.domain.Menu;
import menu.domain.Recommender;
import menu.view.InputView;
import menu.view.OutputView;

public class MenuController {

private final Map<Category, String> menuMap;
private final Recommender recommender;

public MenuController(Map<Category, String> menuMap, Recommender recommender) {
this.menuMap = menuMap;
this.recommender = recommender;
}

public void start() {
OutputView.printStart();
List<Coach> coaches = repeat(this::inputCoachName);
inputCoachCanNotEatMenu(coaches);
List<Category> categories = recommendMenu(coaches);
OutputView.printRecommendResult(categories, coaches);
}

public List<Category> recommendMenu(List<Coach> coaches) {
List<Category> categories = new ArrayList<>();
for (Date date : Date.values()) {
Category category = recommender.pickOneCategory(categories);
for (Coach coach : coaches) {
Menu menu = recommender.recommendMenuForCoach(menuMap, category, coach);
coach.addCanEatMenu(menu);
}
categories.add(category);
}
return categories;
}

private List<Coach> inputCoachName() {
OutputView.inputCoachName();
List<String> coachNames = splitInput(InputView.readCoachName());
return coachNames.stream()
.map(Coach::new)
.collect(Collectors.toList());
}

private List<Menu> inputCanNotEatMenu(Coach coach) {
OutputView.inputMenu(coach);
List<String> menus = splitInput(InputView.readMenus());
return menus.stream()
.map(Menu::of)
.collect(Collectors.toList());
}

private void inputCoachCanNotEatMenu(List<Coach> coaches) {
for (Coach coach : coaches) {
List<Menu> menus = repeat(() -> inputCanNotEatMenu(coach));
if (menus != null) {
coach.addCanNotEatMenu(menus);
}
}
}
Comment on lines +64 to +71

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repeat의 파라미터가 Supplier 형식이라 어떻게 파라미터 있는 메서드를 재입력 로직을 적용할 수 있을까 고민했는데
그냥 파라미터 없고 return 값에 함수를 넣는 방식으로 해결할 수 있었군요


private List<String> splitInput(String input) {
return Arrays.stream(input.split(","))
.collect(Collectors.toUnmodifiableList());
}

private <T> T repeat(Supplier<T> inputReader) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제내릭을 사용하신게 인상깊네요! 덕분에 제내릭에 대해 한 번 알아볼 수 있는 경험이 되었습니다!
다만 메서드 이름을 repeat 보다는 getValidatedValuegetLegalValue 와 같은 식으로 하면 더 좋을 것 같다는 생각이 들었어요!

try {
return inputReader.get();
} catch (IllegalArgumentException e) {
OutputView.printError(e);
return repeat(inputReader);
}
}
}
35 changes: 35 additions & 0 deletions src/main/java/menu/domain/Category.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package menu.domain;

import java.util.Arrays;

public enum Category {
JAPAN(1, "일식"),
KOREAN(2, "한식"),
CHINA(3, "중식"),
ASIAN(4, "아시안"),
WESTERN(5, "양식");

private final int value;
private final String type;

Category(int value, String type) {
this.value = value;
this.type = type;
}

public static Category valueOf(int number) {
return Arrays.stream(Category.values())
.filter(category -> category.getValue() == number)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("[ERROR] 유효하지 않은 값 입니다."));
}

public int getValue() {
return value;
}

@Override
public String toString() {
return type;
}
}
68 changes: 68 additions & 0 deletions src/main/java/menu/domain/Coach.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package menu.domain;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class Coach {

private static final int COACH_NAME_MIN_SIZE = 2;
private static final int COACH_NAME_MAX_SIZE = 4;

private final String name;
private final List<Menu> canNotEat = new ArrayList<>();
private final List<Menu> canEat = new ArrayList<>();

public Coach(String name) {
validateCoachNameSize(name);
this.name = name;
}

public void addCanNotEatMenu(List<Menu> menus) {
canNotEat.addAll(menus);
}

public void addCanEatMenu(Menu menu) {
canEat.add(menu);
}

public boolean canNotEatMenu(Menu menu) {
return canNotEat.contains(menu);
}

public boolean isAlreadyRecommended(Menu menu) {
return canEat.contains(menu);
}

private void validateCoachNameSize(String name) {
if (name.length() < COACH_NAME_MIN_SIZE || name.length() > COACH_NAME_MAX_SIZE) {
throw new IllegalArgumentException("[ERROR] 코치의 이름은 최소 2글자, 최대 4글자 입니다.");
}
}

public List<Menu> getCanEat() {
return canEat;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Coach coach = (Coach) o;
return Objects.equals(name, coach.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}

@Override
public String toString() {
return name;
}
}
29 changes: 29 additions & 0 deletions src/main/java/menu/domain/Date.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package menu.domain;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public enum Date {
MON("월요일"),
TUE("화요일"),
WED("수요일"),
THI("목요일"),
FRI("금요일");

private final String date;

Date(String date) {
this.date = date;
}

public static List<String> getDateOfWeek() {
return Arrays.stream(Date.values())
.map(Date::getDate)
.collect(Collectors.toList());
}

public String getDate() {
return date;
}
}
59 changes: 59 additions & 0 deletions src/main/java/menu/domain/Menu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package menu.domain;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class Menu {

private final String menu;

private Menu(String menu) {
this.menu = menu;
}

public static Menu of(String menu) {
return new Menu(menu);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

왜 생성자를 사용하지 않고 of 라는 메서드를 만들어서 활용하신지 궁금합니다🤔

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한번 정적 팩토리 메서드 연습겸 사용해봤습니다 ㅋㅋㅋㅋ 이유는 없습니다. 그냥 생성자 사용해도 될 거 같네요!!


public static Menu findMenuByName(List<String> menus, String menuName) {
List<Menu> totalMenu = toMenu(menus);
return totalMenu.stream()
.filter(menu -> menu.checkMenuName(menuName)).findAny()
.orElseThrow(() -> new IllegalArgumentException("[ERROR] 음식이 존재하지 않습니다."));
}

private static List<Menu> toMenu(List<String> menus) {
return menus.stream().map(Menu::of).collect(Collectors.toList());
}

private boolean checkMenuName(String menuName) {
return this.menu.equals(menuName);
}

public String getMenu() {
return menu;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Menu menu1 = (Menu) o;
return Objects.equals(menu, menu1.menu);
}

@Override
public int hashCode() {
return Objects.hash(menu);
}

@Override
public String toString() {
return menu;
}
}
43 changes: 43 additions & 0 deletions src/main/java/menu/domain/Recommender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package menu.domain;

import static menu.domain.Menu.findMenuByName;

import camp.nextstep.edu.missionutils.Randoms;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Recommender {

private static final int SAME_CATEGORY_MAX_COUNT = 2;
private static final int START_BOUND = 1;

public Menu recommendMenuForCoach(Map<Category, String> map, Category category, Coach coach) {
List<String> categoryMenus = Arrays.stream(map.get(category).split(","))
.collect(Collectors.toList());
while (true) {
Menu menu = findMenuByName(categoryMenus, Randoms.shuffle(categoryMenus).get(0));
if (coach.canNotEatMenu(menu) || coach.isAlreadyRecommended(menu)) {
continue;
}
return menu;
}
}

public Category pickOneCategory(List<Category> recommendedCategories) {
while (true) {
Category category = Category.valueOf(
Randoms.pickNumberInRange(START_BOUND, Category.values().length));
if (!isValidCategory(recommendedCategories, category)) {
continue;
}
return category;
}
}

private boolean isValidCategory(List<Category> recommendedCategories, Category category) {
long count = recommendedCategories.stream().filter(c -> c.equals(category)).count();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한 줄에 . 이 많아서 가독성을 조금 해치는 것 같아요 객체지향 생활체조 원칙 4번을 참고하시면 좋을 것 같아요!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 놓쳤었네요 다 확인했다고 생각했는데 감사합니다!

return count < SAME_CATEGORY_MAX_COUNT;
}
}
Loading