ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,76 @@
+package christmas.domain.plan;
+
+import christmas.exception.DateInputException;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.IntUnaryOperator;
+
+public class EventDate {
+
+    private static final int YEAR = 2023;
+    private static final int MONTH = 12;
+    private static final int MIN_DATE = 1;
+    private static final int MAX_DATE = 31;
+
+    private static final Set<DayOfWeek> WEEKEND = Set.of(DayOfWeek.FRIDAY, DayOfWeek.SATURDAY);
+
+    private final LocalDate date;
+
+    private EventDate(int date) {
+        validate(date);
+        this.date = LocalDate.of(YEAR, MONTH, date);
+    }
+
+    private static void validate(int date) {
+        if (isOutOfRange(date)) {
+            throw new DateInputException("Date is Out of Range : " + date);
+        }
+    }
+
+    private static boolean isOutOfRange(int date) {
+        return date < MIN_DATE || date > MAX_DATE;
+    }
+
+    public static EventDate from(int date) {
+        return new EventDate(date);
+    }
+
+    public boolean isWeekend() {
+        DayOfWeek dayOfWeek = date.getDayOfWeek();
+        return WEEKEND.contains(dayOfWeek);
+    }
+
+    public boolean isWeekDay() {
+        return !isWeekend();
+    }
+
+    public int calculateByDate(IntUnaryOperator function) {
+        return function.applyAsInt(getDate());
+    }
+
+    public int getDate() {
+        return date.getDayOfMonth();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (object == null || getClass() != object.getClass()) {
+            return false;
+        }
+        EventDate comparedDate = (EventDate) object;
+        return Objects.equals(date, comparedDate.date);
+    }
+
+    @Override
+    public int hashCode() {
+        if (date == null) {
+            return 0;
+        }
+        return date.hashCode();
+    }
+}

리뷰: dayOfWeek을 반환하는 하지 않고 평일/주말 여부만 알 수 있도록 하셨네요! 3주차 피드백을 잘 반영하셨네요 배워갑니다👍
@@ -0,0 +1,27 @@
+package christmas.domain.discount.policy;
+
+import christmas.domain.plan.EventSchedule;
+import christmas.domain.plan.Plan;
+import java.util.function.IntUnaryOperator;
+
+public class ChristmasDDayDiscount extends DiscountPolicy {
+
+    private static final int START_DATE = 1;
+    private static final int END_DATE = 25;
+    private static final EventSchedule EVENT_SCHEDULE = EventSchedule.of(START_DATE, END_DATE);
+
+    private static final int DEFAULT_DISCOUNT_AMOUNT = 1_000;
+    private static final int INCREASING_DISCOUNT_AMOUNT = 100;
+    private static final IntUnaryOperator DISCOUNT_FUNCTION_BY_DATE
+            = date -> DEFAULT_DISCOUNT_AMOUNT + (date - 1) * INCREASING_DISCOUNT_AMOUNT;
+
+    @Override
+    boolean isSatisfyPrecondition(Plan plan) {
+        return plan.isContainedBy(EVENT_SCHEDULE);
+    }
+
+    @Override
+    int calculateDiscountAmount(Plan plan) {
+        return plan.calculateByDate(DISCOUNT_FUNCTION_BY_DATE);
+    }
+}

리뷰: 다른 구현체들과 달리 이부분만 함수형으로 작성하신 이유가 있으실까요?
@@ -0,0 +1,47 @@
+package christmas.domain.gift;
+
+import christmas.domain.plan.Menu;
+import java.util.Map;
+import java.util.Objects;
+
+public enum Gift {
+    EVENT_GIFT(Map.of(Menu.CHAMPAGNE, 1)),
+    NOTHING(Map.of());
+
+    private static final int MIN_GIFT_PRICE = 120_000;
+
+    private final Map<Menu, Integer> menuToCount;
+
+    Gift(Map<Menu, Integer> menuToCount) {
+        this.menuToCount = Objects.requireNonNull(menuToCount);
+    }
+
+    public static Gift from(int totalPrice) {
+        if (isOverThanStandardPrice(totalPrice)) {
+            return EVENT_GIFT;
+        }
+        return NOTHING;
+    }
+
+    private static boolean isOverThanStandardPrice(int totalPrice) {
+        return totalPrice >= MIN_GIFT_PRICE;
+    }
+
+    public int calculateBenefitPrice() {
+        return menuToCount.keySet().stream()
+                .mapToInt(menu -> calculatePartOfPrice(menu, menuToCount.get(menu)))
+                .sum();
+    }
+
+    public int calculatePartOfPrice(Menu menu, int count) {
+        return menu.getPrice() * count;
+    }
+
+    public boolean isEmpty() {
+        return menuToCount.isEmpty();
+    }
+
+    public Map<Menu, Integer> getMenuToCount() {
+        return Map.copyOf(menuToCount);
+    }
+}

리뷰: 이 부분은 증정품이 여러개가 될 수 있다는 확장성을 고려하신건가요?👍
@@ -0,0 +1,21 @@
+package christmas.view;
+
+import christmas.domain.badge.Badge;
+import java.util.Map;
+
+class BadgeView {
+
+    private static final Map<Badge, String> BADGE_TO_MESSAGE = Map.of(
+            Badge.SANTA, "산타", Badge.TREE, "트리",
+            Badge.STAR, "별", Badge.NOTHING, "없음");
+
+    private BadgeView() {
+    }
+
+    public static String findView(Badge badge) {
+        if (!BADGE_TO_MESSAGE.containsKey(badge)) {
+            throw new IllegalArgumentException("Badge not enrolled at view; input : " + badge);
+        }
+        return BADGE_TO_MESSAGE.get(badge);
+    }
+}

리뷰: 리뷰이님의 코드를 보고 궁금한게 생겨 어떻게 생각하시는지 여쭤봅니다!
3주차 피드백 중 "view를 위한 로직은 분리한다"가 모델에서는 뷰와 관련된 것들 아무것도 가질 수 없는 걸 의미하면 이 처럼 뷰에서 도메인에 대해 많은 걸 알아야하는 점도 생길 수도 있지 않을까요?
@@ -0,0 +1,79 @@
+package christmas.controller;
+
+import christmas.domain.badge.Badge;
+import christmas.domain.discount.DiscountDetails;
+import christmas.domain.discount.TotalDiscountEvent;
+import christmas.domain.gift.Gift;
+import christmas.domain.plan.EventDate;
+import christmas.domain.plan.Menu;
+import christmas.domain.plan.Order;
+import christmas.domain.plan.Plan;
+import christmas.dto.PlanResultDto;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class ChristmasPlanerController {
+
+    private final TotalDiscountEvent discountEvent = TotalDiscountEvent.createDecemberEvent();
+    private final InputView inputView = new InputView();
+    private final OutputView outputView = new OutputView();
+
+    public void run() {
+        startApplication();
+        Plan plan = inputPlan();
+        PlanResultDto planResult = applyEvents(plan);
+        printEventResult(planResult);
+    }
+
+    private void startApplication() {
+        outputView.printApplicationTitle();
+    }
+
+    private Plan inputPlan() {
+        EventDate date = readRepeatedlyUntilNoException(this::readDate);
+        Order order = readRepeatedlyUntilNoException(this::readOrder);
+        return Plan.of(date, order);
+    }
+
+    private <T> T readRepeatedlyUntilNoException(Supplier<T> supplier) {
+        try {
+            return supplier.get();
+        } catch (IllegalArgumentException exception) {
+            outputView.printExceptionMessage(exception);
+            return readRepeatedlyUntilNoException(supplier);
+        }
+    }
+
+    private EventDate readDate() {
+        int date = inputView.readDate();
+        return EventDate.from(date);
+    }
+
+    private Order readOrder() {
+        Map<Menu, Integer> order = inputView.readOrder();
+        return Order.from(order);
+    }
+
+    private PlanResultDto applyEvents(Plan plan) {
+        DiscountDetails discountDetails = discountEvent.makeDiscountDetails(plan);
+        Gift gift = Gift.from(plan.calculateTotalPrice());
+        int totalBenefitPrice = calculateTotalBenefitPrice(discountDetails, gift);
+        Badge badge = Badge.from(totalBenefitPrice);
+
+        return PlanResultDto.builder()
+                .plan(plan).discountDetails(discountDetails)
+                .gift(gift).badge(badge).build();
+    }
+
+    private int calculateTotalBenefitPrice(DiscountDetails discountDetails, Gift gift) {
+        int totalDiscountPrice = discountDetails.calculateTotalDiscountPrice();
+        int giftBenefitPrice = gift.calculateBenefitPrice();
+        return totalDiscountPrice + giftBenefitPrice;
+    }
+
+    private void printEventResult(PlanResultDto planResult) {
+        outputView.printPlanResult(planResult);
+    }
+}

리뷰: 저도 이런 느낌인데요.
저는 뷰로부터 입력을 요청하는 컨트롤러들을 컨트롤하는 컨트롤러를 만들었습니다 😂
@@ -0,0 +1,79 @@
+package christmas.controller;
+
+import christmas.domain.badge.Badge;
+import christmas.domain.discount.DiscountDetails;
+import christmas.domain.discount.TotalDiscountEvent;
+import christmas.domain.gift.Gift;
+import christmas.domain.plan.EventDate;
+import christmas.domain.plan.Menu;
+import christmas.domain.plan.Order;
+import christmas.domain.plan.Plan;
+import christmas.dto.PlanResultDto;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class ChristmasPlanerController {
+
+    private final TotalDiscountEvent discountEvent = TotalDiscountEvent.createDecemberEvent();
+    private final InputView inputView = new InputView();
+    private final OutputView outputView = new OutputView();
+
+    public void run() {
+        startApplication();
+        Plan plan = inputPlan();
+        PlanResultDto planResult = applyEvents(plan);
+        printEventResult(planResult);
+    }
+
+    private void startApplication() {
+        outputView.printApplicationTitle();
+    }
+
+    private Plan inputPlan() {
+        EventDate date = readRepeatedlyUntilNoException(this::readDate);
+        Order order = readRepeatedlyUntilNoException(this::readOrder);
+        return Plan.of(date, order);
+    }
+
+    private <T> T readRepeatedlyUntilNoException(Supplier<T> supplier) {
+        try {
+            return supplier.get();
+        } catch (IllegalArgumentException exception) {
+            outputView.printExceptionMessage(exception);
+            return readRepeatedlyUntilNoException(supplier);
+        }
+    }
+
+    private EventDate readDate() {
+        int date = inputView.readDate();
+        return EventDate.from(date);
+    }
+
+    private Order readOrder() {
+        Map<Menu, Integer> order = inputView.readOrder();
+        return Order.from(order);
+    }
+
+    private PlanResultDto applyEvents(Plan plan) {
+        DiscountDetails discountDetails = discountEvent.makeDiscountDetails(plan);
+        Gift gift = Gift.from(plan.calculateTotalPrice());
+        int totalBenefitPrice = calculateTotalBenefitPrice(discountDetails, gift);
+        Badge badge = Badge.from(totalBenefitPrice);
+
+        return PlanResultDto.builder()
+                .plan(plan).discountDetails(discountDetails)
+                .gift(gift).badge(badge).build();
+    }
+
+    private int calculateTotalBenefitPrice(DiscountDetails discountDetails, Gift gift) {
+        int totalDiscountPrice = discountDetails.calculateTotalDiscountPrice();
+        int giftBenefitPrice = gift.calculateBenefitPrice();
+        return totalDiscountPrice + giftBenefitPrice;
+    }
+
+    private void printEventResult(PlanResultDto planResult) {
+        outputView.printPlanResult(planResult);
+    }
+}

리뷰: 오 DTO에 빌더 패턴을 사용하셨네요 스프링 어노테이션에서 사용하던 건데 훌륭합니다!
@@ -0,0 +1,27 @@
+package christmas.domain.discount.policy;
+
+import christmas.domain.plan.EventSchedule;
+import christmas.domain.plan.Plan;
+import java.util.function.IntUnaryOperator;
+
+public class ChristmasDDayDiscount extends DiscountPolicy {
+
+    private static final int START_DATE = 1;
+    private static final int END_DATE = 25;
+    private static final EventSchedule EVENT_SCHEDULE = EventSchedule.of(START_DATE, END_DATE);
+
+    private static final int DEFAULT_DISCOUNT_AMOUNT = 1_000;
+    private static final int INCREASING_DISCOUNT_AMOUNT = 100;
+    private static final IntUnaryOperator DISCOUNT_FUNCTION_BY_DATE
+            = date -> DEFAULT_DISCOUNT_AMOUNT + (date - 1) * INCREASING_DISCOUNT_AMOUNT;
+
+    @Override
+    boolean isSatisfyPrecondition(Plan plan) {
+        return plan.isContainedBy(EVENT_SCHEDULE);
+    }
+
+    @Override
+    int calculateDiscountAmount(Plan plan) {
+        return plan.calculateByDate(DISCOUNT_FUNCTION_BY_DATE);
+    }
+}

리뷰: 이번 과제에서 람다를 많이 연습해보고 싶었는데 {() -> } 외에도 이렇게 쓸 수 있겠군요! 좋은 정보 얻어갑니다!
@@ -0,0 +1,29 @@
+package christmas.view;
+
+import christmas.exception.DateInputException;
+import christmas.exception.OnlyDrinkMenuException;
+import christmas.exception.OrderInputException;
+import christmas.exception.TotalMenuCountException;
+import java.util.Map;
+
+class ErrorMessageView {
+
+    private static final String ERROR_MESSAGE_PREFIX = "[ERROR] ";
+    private static final Map<Class<? extends IllegalArgumentException>, String> EXCEPTION_TO_ERROR_MESSAGE
+            = Map.of(DateInputException.class, "유효하지 않은 날짜입니다. 다시 입력해 주세요.",
+            OrderInputException.class, "유효하지 않은 주문입니다. 다시 입력해 주세요.",
+            OnlyDrinkMenuException.class, "주문은 음료로만 구성될 수 없습니다",
+            TotalMenuCountException.class, "총 메뉴 개수는 20개를 초과할 수 없습니다");
+    private static final String DEFAULT_ERROR_MESSAGE = "예상치 못한 에러가 발생했습니다. 관리자에게 문의하세요.";
+
+    private ErrorMessageView() {
+    }
+
+    public static String constructMessage(IllegalArgumentException exception) {
+        return ERROR_MESSAGE_PREFIX.concat(findErrorMessage(exception));
+    }
+
+    private static String findErrorMessage(IllegalArgumentException exception) {
+        return EXCEPTION_TO_ERROR_MESSAGE.getOrDefault(exception.getClass(), DEFAULT_ERROR_MESSAGE);
+    }
+}

리뷰: 각 예외 메시지를 맵으로 담는 방법 정말 기똥차네요...
@@ -0,0 +1,79 @@
+package christmas.controller;
+
+import christmas.domain.badge.Badge;
+import christmas.domain.discount.DiscountDetails;
+import christmas.domain.discount.TotalDiscountEvent;
+import christmas.domain.gift.Gift;
+import christmas.domain.plan.EventDate;
+import christmas.domain.plan.Menu;
+import christmas.domain.plan.Order;
+import christmas.domain.plan.Plan;
+import christmas.dto.PlanResultDto;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class ChristmasPlanerController {
+
+    private final TotalDiscountEvent discountEvent = TotalDiscountEvent.createDecemberEvent();
+    private final InputView inputView = new InputView();
+    private final OutputView outputView = new OutputView();
+
+    public void run() {
+        startApplication();
+        Plan plan = inputPlan();
+        PlanResultDto planResult = applyEvents(plan);
+        printEventResult(planResult);
+    }
+
+    private void startApplication() {
+        outputView.printApplicationTitle();
+    }
+
+    private Plan inputPlan() {
+        EventDate date = readRepeatedlyUntilNoException(this::readDate);
+        Order order = readRepeatedlyUntilNoException(this::readOrder);
+        return Plan.of(date, order);
+    }
+
+    private <T> T readRepeatedlyUntilNoException(Supplier<T> supplier) {
+        try {
+            return supplier.get();
+        } catch (IllegalArgumentException exception) {
+            outputView.printExceptionMessage(exception);
+            return readRepeatedlyUntilNoException(supplier);
+        }
+    }
+
+    private EventDate readDate() {
+        int date = inputView.readDate();
+        return EventDate.from(date);
+    }
+
+    private Order readOrder() {
+        Map<Menu, Integer> order = inputView.readOrder();
+        return Order.from(order);
+    }
+
+    private PlanResultDto applyEvents(Plan plan) {
+        DiscountDetails discountDetails = discountEvent.makeDiscountDetails(plan);
+        Gift gift = Gift.from(plan.calculateTotalPrice());
+        int totalBenefitPrice = calculateTotalBenefitPrice(discountDetails, gift);
+        Badge badge = Badge.from(totalBenefitPrice);
+
+        return PlanResultDto.builder()
+                .plan(plan).discountDetails(discountDetails)
+                .gift(gift).badge(badge).build();
+    }
+
+    private int calculateTotalBenefitPrice(DiscountDetails discountDetails, Gift gift) {
+        int totalDiscountPrice = discountDetails.calculateTotalDiscountPrice();
+        int giftBenefitPrice = gift.calculateBenefitPrice();
+        return totalDiscountPrice + giftBenefitPrice;
+    }
+
+    private void printEventResult(PlanResultDto planResult) {
+        outputView.printPlanResult(planResult);
+    }
+}

리뷰: Plan 객체를 생성할 때 EventDate와 Order 객체를 한 번에 넣어서 생성하는 것이 깔끔하기는 하지만 EventDate와 Order를 생성시 검증하는데 방문 날짜와 주문 도메인이 컨트롤러에 노출되는것이 아닌가 생각이 드는데요, 이 부분은 어떻게 생각하시나요?
@@ -0,0 +1,79 @@
+package christmas.controller;
+
+import christmas.domain.badge.Badge;
+import christmas.domain.discount.DiscountDetails;
+import christmas.domain.discount.TotalDiscountEvent;
+import christmas.domain.gift.Gift;
+import christmas.domain.plan.EventDate;
+import christmas.domain.plan.Menu;
+import christmas.domain.plan.Order;
+import christmas.domain.plan.Plan;
+import christmas.dto.PlanResultDto;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class ChristmasPlanerController {
+
+    private final TotalDiscountEvent discountEvent = TotalDiscountEvent.createDecemberEvent();
+    private final InputView inputView = new InputView();
+    private final OutputView outputView = new OutputView();
+
+    public void run() {
+        startApplication();
+        Plan plan = inputPlan();
+        PlanResultDto planResult = applyEvents(plan);
+        printEventResult(planResult);
+    }
+
+    private void startApplication() {
+        outputView.printApplicationTitle();
+    }
+
+    private Plan inputPlan() {
+        EventDate date = readRepeatedlyUntilNoException(this::readDate);
+        Order order = readRepeatedlyUntilNoException(this::readOrder);
+        return Plan.of(date, order);
+    }
+
+    private <T> T readRepeatedlyUntilNoException(Supplier<T> supplier) {
+        try {
+            return supplier.get();
+        } catch (IllegalArgumentException exception) {
+            outputView.printExceptionMessage(exception);
+            return readRepeatedlyUntilNoException(supplier);
+        }
+    }
+
+    private EventDate readDate() {
+        int date = inputView.readDate();
+        return EventDate.from(date);
+    }
+
+    private Order readOrder() {
+        Map<Menu, Integer> order = inputView.readOrder();
+        return Order.from(order);
+    }
+
+    private PlanResultDto applyEvents(Plan plan) {
+        DiscountDetails discountDetails = discountEvent.makeDiscountDetails(plan);
+        Gift gift = Gift.from(plan.calculateTotalPrice());
+        int totalBenefitPrice = calculateTotalBenefitPrice(discountDetails, gift);
+        Badge badge = Badge.from(totalBenefitPrice);
+
+        return PlanResultDto.builder()
+                .plan(plan).discountDetails(discountDetails)
+                .gift(gift).badge(badge).build();
+    }
+
+    private int calculateTotalBenefitPrice(DiscountDetails discountDetails, Gift gift) {
+        int totalDiscountPrice = discountDetails.calculateTotalDiscountPrice();
+        int giftBenefitPrice = gift.calculateBenefitPrice();
+        return totalDiscountPrice + giftBenefitPrice;
+    }
+
+    private void printEventResult(PlanResultDto planResult) {
+        outputView.printPlanResult(planResult);
+    }
+}

리뷰: 마찬가지로 컨트롤러에서 방문날짜와 주문을 가지고있는 Plan 객체를 통해 할인을 적용하시는데,
할인 적용이라는 비즈니스 로직이 노출되어있는 것이 아닌가 생각이 듭니다.
서비스 레이어에서 처리하지 않은 이유가 있으신지 궁금합니다.
@@ -0,0 +1,33 @@
+package christmas.domain.badge;
+
+import java.util.List;
+
+public enum Badge {
+
+    SANTA(20_000),
+    TREE(10_000),
+    STAR(5_000),
+    NOTHING(0);
+
+    private static final List<Badge> ORDER_BY_BENEFIT = List.of(SANTA, TREE, STAR, NOTHING);
+
+    private final int minBenefitAmount;
+
+    Badge(int minBenefitAmount) {
+        this.minBenefitAmount = minBenefitAmount;
+    }
+
+    public static Badge from(int benefitAmount) {
+        return ORDER_BY_BENEFIT.stream()
+                .filter(badge -> badge.isReach(benefitAmount))
+                .findFirst().orElseThrow(Badge::createIllegalBenefitAmountException);
+    }
+
+    private static IllegalArgumentException createIllegalBenefitAmountException() {
+        return new IllegalArgumentException("benefit amount must not be negative");
+    }
+
+    private boolean isReach(int benefitAmount) {
+        return benefitAmount >= this.minBenefitAmount;
+    }
+}

리뷰: values() 등으로 가져오면 순서가 제가 작성한 순서에 영향을 받는다고 생각해서 일부러 `ORDER_BY_BENEFIT`을 만들었습니다. 그런데 다른 코드들을 보니 `Arrays.stream(values()).sort(this::price)` 형식으로 많이 사용 해야 겠더라구요.
@@ -0,0 +1,27 @@
+package christmas.domain.discount.policy;
+
+import christmas.domain.plan.EventSchedule;
+import christmas.domain.plan.Plan;
+import java.util.function.IntUnaryOperator;
+
+public class ChristmasDDayDiscount extends DiscountPolicy {
+
+    private static final int START_DATE = 1;
+    private static final int END_DATE = 25;
+    private static final EventSchedule EVENT_SCHEDULE = EventSchedule.of(START_DATE, END_DATE);
+
+    private static final int DEFAULT_DISCOUNT_AMOUNT = 1_000;
+    private static final int INCREASING_DISCOUNT_AMOUNT = 100;
+    private static final IntUnaryOperator DISCOUNT_FUNCTION_BY_DATE
+            = date -> DEFAULT_DISCOUNT_AMOUNT + (date - 1) * INCREASING_DISCOUNT_AMOUNT;
+
+    @Override
+    boolean isSatisfyPrecondition(Plan plan) {
+        return plan.isContainedBy(EVENT_SCHEDULE);
+    }
+
+    @Override
+    int calculateDiscountAmount(Plan plan) {
+        return plan.calculateByDate(DISCOUNT_FUNCTION_BY_DATE);
+    }
+}

리뷰: 도메인끼리 정보를 주고 받는데 getter를 안쓰는 것이 개인적인 이번 과제 목표였습니다. 그래서 date 라는 int 값을 받아오기 보다, date를 통해 계산하는 식을 넘겨주었습니다.

다시 생각해보면 이정도는 getter로 써도 되지 않나 싶지만, 그냥 최대한 객체답게 사용하는 형식으로 해보았습니다!
@@ -0,0 +1,47 @@
+package christmas.domain.gift;
+
+import christmas.domain.plan.Menu;
+import java.util.Map;
+import java.util.Objects;
+
+public enum Gift {
+    EVENT_GIFT(Map.of(Menu.CHAMPAGNE, 1)),
+    NOTHING(Map.of());
+
+    private static final int MIN_GIFT_PRICE = 120_000;
+
+    private final Map<Menu, Integer> menuToCount;
+
+    Gift(Map<Menu, Integer> menuToCount) {
+        this.menuToCount = Objects.requireNonNull(menuToCount);
+    }
+
+    public static Gift from(int totalPrice) {
+        if (isOverThanStandardPrice(totalPrice)) {
+            return EVENT_GIFT;
+        }
+        return NOTHING;
+    }
+
+    private static boolean isOverThanStandardPrice(int totalPrice) {
+        return totalPrice >= MIN_GIFT_PRICE;
+    }
+
+    public int calculateBenefitPrice() {
+        return menuToCount.keySet().stream()
+                .mapToInt(menu -> calculatePartOfPrice(menu, menuToCount.get(menu)))
+                .sum();
+    }
+
+    public int calculatePartOfPrice(Menu menu, int count) {
+        return menu.getPrice() * count;
+    }
+
+    public boolean isEmpty() {
+        return menuToCount.isEmpty();
+    }
+
+    public Map<Menu, Integer> getMenuToCount() {
+        return Map.copyOf(menuToCount);
+    }
+}

리뷰: 넵! 확장성을 고려했습니다!
@@ -0,0 +1,21 @@
+package christmas.view;
+
+import christmas.domain.badge.Badge;
+import java.util.Map;
+
+class BadgeView {
+
+    private static final Map<Badge, String> BADGE_TO_MESSAGE = Map.of(
+            Badge.SANTA, "산타", Badge.TREE, "트리",
+            Badge.STAR, "별", Badge.NOTHING, "없음");
+
+    private BadgeView() {
+    }
+
+    public static String findView(Badge badge) {
+        if (!BADGE_TO_MESSAGE.containsKey(badge)) {
+            throw new IllegalArgumentException("Badge not enrolled at view; input : " + badge);
+        }
+        return BADGE_TO_MESSAGE.get(badge);
+    }
+}

리뷰: 맞습니다. 이렇게 되면 View가 굉장히 커지게 됩니다. 평소에 저도 enum에서 관리했는데 이번주 과제에는 요구사항을 극한으로 챙기고 싶었습니다.

질문하신 부분이 굉장히 날카로우시네요. "따지지 않고 요구 사항을 최대한 지켜보자"는 부분은 전부 언급하신 것 같습니다. 날카로운 리뷰 감사드립니다.