-
Notifications
You must be signed in to change notification settings - Fork 0
크리스마스 프로모션 코드리뷰 ❤︎ #3
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
base: review
Are you sure you want to change the base?
Changes from all commits
458390f
dda1629
93d479d
c4b3f57
7348967
5740eca
2d8b947
f316079
2051bd8
34278c2
d635d5a
f6128a0
2a2b914
5964d2a
b846c84
8a5315f
8d32d1b
c72259d
28e6c7b
4dd2354
6db824c
2aa1540
8040843
4cbd860
7b72f51
c843042
d7ee9a5
7478ce7
7104f84
1980077
ef144b0
faf7180
31756da
621f1e5
0b459f4
199decb
beed5d2
8ba6c6b
1674d67
4aa7987
cb8a590
6c0699a
707987c
5c95e7a
463b869
bda4060
c9c4a1e
f6b82a8
16b5a90
f48514a
6c06c6c
6767d11
d3eef97
1ebd858
dbfde2c
bc84751
22388c9
4d2c39f
e58a4a7
f511aa4
50f2acf
d04ecfa
d1e7646
623d930
096e0b2
f291f05
044843e
e14a14c
fd02ff8
0673753
e1cea10
d2cb171
6b59e94
34149ba
e4bf21e
5fcf9f6
2c82df4
aa69771
6b09fbc
40e798c
2c85a8f
3ed8499
7d4b1f6
2516698
bf119a1
917f3ee
8fdae14
4b9c148
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| # 구현 기능 목록 | ||
|
|
||
|
|
||
| - [X] 이벤트 플래너 시작 메세지를 출력한다. | ||
| ``` | ||
| 안녕하세요! 우테코 식당 12월 이벤트 플래너입니다. | ||
| ``` | ||
|
|
||
|
|
||
| - [X] 방문 날짜를 입력받고 예외 처리 한다. | ||
| - [X] 예외 처리 | ||
| 1. [X] 1 이상 31 이하의 숫자가 아닌 경우 | ||
| "[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요." 예외메세지 출력 | ||
| + [X] 모든 에러 메시지는 "[ERROR]"로 시작 | ||
| ``` | ||
| 12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!) | ||
| ``` | ||
|
|
||
|
|
||
| - [X] 메뉴와 개수를 입력받고 예외 처리 한다. | ||
| - [X] 예외 처리 | ||
| 1. [X] 고객이 메뉴판에 없는 메뉴를 입력하는 경우 | ||
| "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요." | ||
| 2. [X] 메뉴의 개수는 1 이상의 숫자가 아닌 경우 | ||
| "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요." | ||
| 3. [X] 메뉴 형식이 예시와 다른 경우 | ||
| "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요." | ||
| 4. [X] 중복 메뉴를 입력한 경우(e.g. 시저샐러드-1,시저샐러드-1) | ||
| "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요." | ||
| 5. [X] 음료만 주문한 경우 | ||
| "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요." | ||
| 6. [X] 메뉴 총개수가 20개 초과인 경우 | ||
| "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요." | ||
| + [X] 모든 에러 메시지는 "[ERROR]"로 시작 | ||
| ``` | ||
| 주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1) | ||
| ``` | ||
|
|
||
|
|
||
| - [X] 결과 시작 메세지를 출력한다. | ||
|
|
||
| ``` | ||
| 12월 26일에 우테코 식당에서 받을 이벤트 혜택 미리 보기! | ||
|
|
||
| ``` | ||
|
|
||
|
|
||
| - [X] 주문 메뉴를 출력한다. | ||
| - 출력 순서는 자유롭게 | ||
|
|
||
|
|
||
| - [X] 할인 전 총주문 금액을 계산한다. | ||
|
|
||
|
|
||
| - [X] 할인 전 총주문 금액을 출력한다. | ||
|
|
||
|
|
||
| - [X] 적용되는 이벤트를 계산한다. | ||
|
|
||
|
|
||
| - [X] 증정 메뉴를 출력한다. | ||
| - [X] 증정 이벤트에 해당하지 않는 경우, 증정 메뉴 "없음" 출력 | ||
|
|
||
|
|
||
| - [X] 혜택 내역을 출력한다. | ||
| - [X] 고객에게 적용된 이벤트만 출력 | ||
| - [X] 적용된 이벤트가 하나도 없다면 혜택 내역 "없음"으로 출력 | ||
| - [X] 혜택 내역에 여러 개의 이벤트가 적용된 경우, 출력 순서는 자유롭게 출력 | ||
|
|
||
|
|
||
| - [X] 할인 받는 금액을 계산한다. | ||
|
|
||
|
|
||
| - [X] 총혜택 금액을 출력한다. | ||
| - 총혜택 금액 = 할인 금액의 합계 + 증정 메뉴의 가격 | ||
|
|
||
|
|
||
| - [X] 할인 후 예상 결제 금액을 출력한다. | ||
| - 할인 후 예상 결제 금액 = 할인 전 총주문 금액 - 할인 금액 | ||
|
|
||
|
|
||
| - [X] 12월 이벤트 배지를 출력한다. | ||
| - 총혜택 금액에 따라 이벤트 배지의 이름을 다르게 출력 | ||
|
|
||
|
|
||
| - [X] 예외 처리 관련 | ||
| - IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. | ||
|
|
||
|
|
||
| --- | ||
| ### 전체 출력 예시 | ||
| ``` | ||
| 안녕하세요! 우테코 식당 12월 이벤트 플래너입니다. | ||
| 12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!) | ||
| 3 | ||
| 주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1) | ||
| 티본스테이크-1,바비큐립-1,초코케이크-2,제로콜라-1 | ||
| 12월 3일에 우테코 식당에서 받을 이벤트 혜택 미리 보기! | ||
|
|
||
| <주문 메뉴> | ||
| 티본스테이크 1개 | ||
| 바비큐립 1개 | ||
| 초코케이크 2개 | ||
| 제로콜라 1개 | ||
|
|
||
| <할인 전 총주문 금액> | ||
| 142,000원 | ||
|
|
||
| <증정 메뉴> | ||
| 샴페인 1개 | ||
|
|
||
| <혜택 내역> | ||
| 크리스마스 디데이 할인: -1,200원 | ||
| 평일 할인: -4,046원 | ||
| 특별 할인: -1,000원 | ||
| 증정 이벤트: -25,000원 | ||
|
|
||
| <총혜택 금액> | ||
| -31,246원 | ||
|
|
||
| <할인 후 예상 결제 금액> | ||
| 135,754원 | ||
|
|
||
| <12월 이벤트 배지> | ||
| 산타 | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,14 @@ | ||
| package christmas; | ||
|
|
||
| import camp.nextstep.edu.missionutils.Console; | ||
| import christmas.controller.ChristmasController; | ||
| import christmas.view.InputView; | ||
| import christmas.view.OutputView; | ||
|
|
||
| public class Application { | ||
| public static void main(String[] args) { | ||
| // TODO: 프로그램 구현 | ||
| ChristmasController christmasController = ChristmasController.of(InputView.getInstance(), OutputView.getInstance()); | ||
| christmasController.run(); | ||
| Console.close(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package christmas.controller; | ||
|
|
||
| import christmas.domain.orders.OrderItem; | ||
| import christmas.domain.orders.Orders; | ||
| import christmas.domain.visitingDate.VisitingDate; | ||
| import christmas.dto.OrderItemDto; | ||
| import christmas.dto.OrdersDto; | ||
| import christmas.dto.ResultDto; | ||
| import christmas.service.ChristmasManager; | ||
| import christmas.view.InputView; | ||
| import christmas.view.OutputView; | ||
|
|
||
| import java.util.List; | ||
| import java.util.function.Supplier; | ||
|
|
||
| public class ChristmasController { | ||
| private final InputView inputView; | ||
| private final OutputView outputView; | ||
| private ChristmasManager christmasManager; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. VisitingDateInputHandler 와 OrdersInputHandler 는 어떤 이유에서 final 처리 하지 않은 것인지 궁금합니다! |
||
|
|
||
| private ChristmasController(InputView inputView, OutputView outputView) { | ||
| this.inputView = inputView; | ||
| this.outputView = outputView; | ||
| } | ||
|
|
||
| public static ChristmasController of(InputView inputView, OutputView outputView) { | ||
| return new ChristmasController(inputView, outputView); | ||
| } | ||
|
|
||
| public void run() { | ||
| initializeChristmasManager(); | ||
| printOrders(); | ||
| printResult(); | ||
| } | ||
|
|
||
| private void initializeChristmasManager() { | ||
| outputView.printStart(); | ||
| VisitingDate date = createVisitingDate(); | ||
| Orders orders = createOrders(); | ||
| christmasManager = ChristmasManager.of(date, orders); | ||
| outputView.printResultStart(date.provideDate()); | ||
| } | ||
|
|
||
| private VisitingDate createVisitingDate() { | ||
| return readUserInput(() -> { | ||
| int input = inputView.readVisitingDate(); | ||
| return VisitingDate.from(input); | ||
| }); | ||
| } | ||
|
|
||
| private Orders createOrders() { | ||
| return readUserInput(() -> { | ||
| List<OrderItem> items = inputView.readOrders().stream() | ||
| .map(OrderItemDto::toOrderItem) | ||
| .toList(); | ||
| return Orders.from(items); | ||
| }); | ||
| } | ||
|
|
||
| private <T> T readUserInput(Supplier<T> supplier) { | ||
| while (true) { | ||
| try { | ||
| return supplier.get(); | ||
| } catch (IllegalArgumentException e) { | ||
| outputView.printError(e.getMessage()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void printOrders() { | ||
| OrdersDto ordersDto = christmasManager.createOrdersDto(); | ||
| outputView.printMenu(ordersDto); | ||
| } | ||
|
|
||
| private void printResult() { | ||
| ResultDto resultDto = christmasManager.createResultDto(); | ||
| outputView.printResult(resultDto); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package christmas.domain.badge; | ||
|
|
||
| import java.util.Arrays; | ||
|
|
||
| public enum BadgeCondition { | ||
| NONE("없음", 0L), | ||
| STAR("별", 5_000L), | ||
| TREE("트리", 10_000L), | ||
| SANTA("산타", 20_000L); | ||
|
|
||
| private final String badgeName; | ||
| private final long minCondition; | ||
|
|
||
| BadgeCondition(String badgeName, long minCondition) { | ||
| this.badgeName = badgeName; | ||
| this.minCondition = minCondition; | ||
| } | ||
|
|
||
| public static BadgeCondition findBadgeByCondition(long totalBenefitAmount) { | ||
| return Arrays.stream(BadgeCondition.values()) | ||
| .filter(badge -> totalBenefitAmount >= badge.getMinCondition()) | ||
| .findFirst() | ||
| .orElse(NONE); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 로직에서는 음수인 값이( 입력 예외 상황 ) 들어오면 배지가 없음을 나타내는 식으로 구현하셨는데, |
||
| } | ||
|
|
||
| public String getBadgeName() { | ||
| return badgeName; | ||
| } | ||
|
|
||
| public long getMinCondition() { | ||
| return minCondition; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package christmas.domain.constants; | ||
|
|
||
| import christmas.domain.orders.MenuItem; | ||
|
|
||
| public final class BenefitConstants { | ||
| public static final long BASE_DISCOUNT = 1_000L; | ||
| public static final long CHRISTMAS_RATE = 100L; | ||
| public static final long DAILY_RATE = 2_023L; | ||
| public static final long BASE_PRICE_CONDITION = 10_000L; | ||
| public static final long GIVE_AWAY_PRICE_CONDITION = 120_000L; | ||
| public static final long GIVE_AWAY_PRICE = MenuItem.샴페인.getPrice(); | ||
| public static final String GIVE_AWAY_ITEM = MenuItem.샴페인.name(); | ||
| public static final int GIVE_AWAY_QUANTITY = 1; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package christmas.domain.constants; | ||
|
|
||
| public final class DateConstants { | ||
| public static final int EVENT_START = 1; | ||
| public static final int EVENT_END = 31; | ||
| public static final int CHRISTMAS_EVENT_END = 25; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| package christmas.domain.event; | ||
|
|
||
| import christmas.domain.orders.Orders; | ||
| import christmas.domain.visitingDate.VisitingDate; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.function.BiFunction; | ||
| import java.util.function.Function; | ||
| import java.util.function.Predicate; | ||
|
|
||
| import static christmas.domain.constants.DateConstants.*; | ||
| import static christmas.domain.constants.BenefitConstants.*; | ||
| import static christmas.domain.orders.MenuCategory.DESSERT; | ||
| import static christmas.domain.orders.MenuCategory.MAIN; | ||
|
|
||
| public enum EventDetail { | ||
| CHRISTMAS_D_DAY( | ||
| "크리스마스 디데이 할인", | ||
| VisitingDate::isChristmasEventActive, | ||
| BASE_PRICE_CONDITION, | ||
| orders -> true, | ||
| (date, orders) -> BASE_DISCOUNT + calculatePassedDays(date) * CHRISTMAS_RATE), | ||
| WEEKDAYS( | ||
| "평일 할인", | ||
| VisitingDate::isWeekday, | ||
| BASE_PRICE_CONDITION, | ||
| orders -> orders.existsOrderItemByCategory(DESSERT), | ||
| (date, orders) -> orders.countOrderItemByCategory(DESSERT) * DAILY_RATE), | ||
| WEEKENDS( | ||
| "주말 할인", | ||
| VisitingDate::isWeekend, | ||
| BASE_PRICE_CONDITION, | ||
| orders -> orders.existsOrderItemByCategory(MAIN), | ||
| (date, orders) -> orders.countOrderItemByCategory(MAIN) * DAILY_RATE), | ||
| SPECIAL( | ||
| "특별 할인", | ||
| VisitingDate::isSpecialDay, | ||
| BASE_PRICE_CONDITION, | ||
| orders -> true, | ||
| (date, orders) -> BASE_DISCOUNT), | ||
| GIVE_AWAY( | ||
| "증정 이벤트", | ||
| VisitingDate::meetsGiveAwayDateCondition, | ||
| GIVE_AWAY_PRICE_CONDITION, | ||
| orders -> true, | ||
| (date, orders) -> GIVE_AWAY_PRICE); | ||
|
|
||
| private final String eventName; | ||
| private final Predicate<VisitingDate> dateCondition; | ||
| private final long priceCondition; | ||
| private final Function<Orders, Boolean> itemCondition; | ||
| private final BiFunction<VisitingDate, Orders, Long> benefitCalculator; | ||
|
|
||
| EventDetail(String eventName, Predicate<VisitingDate> dateCondition, long priceCondition, | ||
| Function<Orders, Boolean> itemCondition, | ||
| BiFunction<VisitingDate, Orders, Long> benefitCalculator) { | ||
| this.eventName = eventName; | ||
| this.dateCondition = dateCondition; | ||
| this.priceCondition = priceCondition; | ||
| this.itemCondition = itemCondition; | ||
| this.benefitCalculator = benefitCalculator; | ||
| } | ||
|
|
||
| private static long calculatePassedDays(VisitingDate date) { | ||
| return date.provideDate() - EVENT_START; | ||
| } | ||
|
|
||
| public static List<MatchingEvent> findByCondition(VisitingDate date, Orders orders) { | ||
| return Arrays.stream(EventDetail.values()) | ||
| .filter(condition -> condition.dateCondition.test(date)) | ||
| .filter(condition -> orders.calculateOriginalTotalAmount() >= condition.priceCondition) | ||
| .filter(condition -> condition.itemCondition.apply(orders)) | ||
| .map(eventDetail -> MatchingEvent.of(eventDetail, eventDetail.calculateBenefitAmount(date, orders))) | ||
| .toList(); | ||
| } | ||
|
|
||
| private long calculateBenefitAmount(VisitingDate date, Orders orders) { | ||
| return benefitCalculator.apply(date, orders); | ||
| } | ||
|
|
||
| public String getEventName() { | ||
| return eventName; | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이벤트를 이렇게 Enum으로 싹다 모아서 관리하니 너무 보기 편하고 응집되어 있는 코드 느낌이 나네요. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 컨트롤러에 모든 비즈니스 로직의 운영 책임이 몰려있군요!
그렇다면, 컨트롤러 Layer에서 단위테스트를 계획하게 된다면, 문제가 발생하지 않을까요?
단위테스트를 계획했지만, 실상 통합테스트 환경에서 테스트하게 되니까요!