ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,57 @@
+package christmas.domain.calendar;
+
+import christmas.domain.event.ChristmasDailyDiscountEvent;
+import christmas.domain.event.Event;
+import christmas.domain.event.EventBadge;
+import christmas.domain.event.FreeGiftEvent;
+import christmas.domain.event.SpecialDiscountEvent;
+import christmas.domain.event.WeekdayDiscountEvent;
+import christmas.domain.event.WeekendDiscountEvent;
+import java.util.Collections;
+import java.util.List;
+
+public class Planner {
+
+    private static final int CHRISTMAS_EVENT_MIN_ORDER_AMOUNT = 10_000;
+    private static final int START_BENEFIT_AMOUNT = 0;
+    private final List<Event> events;
+    private int afterAmount;
+    private EventBadge eventBadge;
+
+    public Planner(Date date, Order order) {
+        if (order.getBeforeMoney() < CHRISTMAS_EVENT_MIN_ORDER_AMOUNT) {
+            this.events = Collections.EMPTY_LIST;
+            this.afterAmount = START_BENEFIT_AMOUNT;
+            return;
+        }
+
+        this.events = List.of(
+                new ChristmasDailyDiscountEvent(),
+                new WeekdayDiscountEvent(),
+                new WeekendDiscountEvent(),
+                new FreeGiftEvent(),
+                new SpecialDiscountEvent()
+        );
+        this.afterAmount = calculateBenefits(date, order);
+    }
+
+    public int calculateBenefits(Date date, Order order) {
+
+        return events.stream()
+                .filter(event -> event.isApplicable(date))
+                .mapToInt(event -> event.calculateDiscount(date, order))
+                .sum();
+    }
+
+    public List<Event> getEvents() {
+        return events;
+    }
+
+    public int getAfterAmount() {
+        return afterAmount;
+    }
+
+    public EventBadge getEventBadge() {
+        return EventBadge.findByBadgeType(afterAmount);
+    }
+}

리뷰: 오호 여기서 이벤트 객체를 넣어서 관리할 수 있네요! 유지보수하기 좋아 보입니다.
@@ -0,0 +1,57 @@
+package christmas.domain.calendar;
+
+import christmas.domain.event.ChristmasDailyDiscountEvent;
+import christmas.domain.event.Event;
+import christmas.domain.event.EventBadge;
+import christmas.domain.event.FreeGiftEvent;
+import christmas.domain.event.SpecialDiscountEvent;
+import christmas.domain.event.WeekdayDiscountEvent;
+import christmas.domain.event.WeekendDiscountEvent;
+import java.util.Collections;
+import java.util.List;
+
+public class Planner {
+
+    private static final int CHRISTMAS_EVENT_MIN_ORDER_AMOUNT = 10_000;
+    private static final int START_BENEFIT_AMOUNT = 0;
+    private final List<Event> events;
+    private int afterAmount;
+    private EventBadge eventBadge;
+
+    public Planner(Date date, Order order) {
+        if (order.getBeforeMoney() < CHRISTMAS_EVENT_MIN_ORDER_AMOUNT) {
+            this.events = Collections.EMPTY_LIST;
+            this.afterAmount = START_BENEFIT_AMOUNT;
+            return;
+        }
+
+        this.events = List.of(
+                new ChristmasDailyDiscountEvent(),
+                new WeekdayDiscountEvent(),
+                new WeekendDiscountEvent(),
+                new FreeGiftEvent(),
+                new SpecialDiscountEvent()
+        );
+        this.afterAmount = calculateBenefits(date, order);
+    }
+
+    public int calculateBenefits(Date date, Order order) {
+
+        return events.stream()
+                .filter(event -> event.isApplicable(date))
+                .mapToInt(event -> event.calculateDiscount(date, order))
+                .sum();
+    }
+
+    public List<Event> getEvents() {
+        return events;
+    }
+
+    public int getAfterAmount() {
+        return afterAmount;
+    }
+
+    public EventBadge getEventBadge() {
+        return EventBadge.findByBadgeType(afterAmount);
+    }
+}

리뷰: 여기서 궁금한게 
```java 
public sealed interface Event permits ChristmasDailyDiscountEvent, WeekdayDiscountEvent, WeekendDiscountEvent,
        SpecialDiscountEvent,
        FreeGiftEvent { ... }
```

이렇게 명시해두었는데, List.of 부분을 변경하면 Event 인터페이스도 수정해야 하나요?
@@ -0,0 +1,55 @@
+package christmas.domain.event;
+
+import christmas.domain.calendar.Date;
+import christmas.domain.calendar.Order;
+import christmas.domain.menu.Menu;
+import christmas.domain.menu.MenuType;
+
+public final class FreeGiftEvent implements Event {
+
+    private static final int CHRISTMAS_EVENT_MONTH = 12;
+    private static final int CHRISTMAS_EVENT_MIN_DAY = 1;
+    private static final int CHRISTMAS_EVENT_MAX_DAY = 31;
+    private static final int CHRISTMAS_EVENT_FREE_STANDARD = 120_000;
+    private static final String CHRISTMAS_EVENT_FREE_QUANTITY = "1";
+    private static final String FREE_GIFT_EVENT_TITLE = "증정 이벤트";
+    private Menu benefitGift = new Menu(MenuType.EMPTY.getTitle(), CHRISTMAS_EVENT_FREE_QUANTITY);
+    private int benefit;
+
+    @Override
+    public boolean isApplicable(Date date) {
+        return isEventPeriod(date);
+    }
+
+    private static boolean isEventPeriod(Date date) {
+        return date.getMonth() == CHRISTMAS_EVENT_MONTH &&
+                date.getDay() >= CHRISTMAS_EVENT_MIN_DAY
+                && date.getDay() <= CHRISTMAS_EVENT_MAX_DAY;
+    }
+
+    @Override
+    public int calculateDiscount(Date date, Order order) {
+        int beforeMoney = order.getBeforeMoney();
+        if (beforeMoney >= CHRISTMAS_EVENT_FREE_STANDARD) {
+            MenuType champagne = MenuType.CHAMPAGNE;
+            benefitGift = new Menu(champagne.getTitle(), CHRISTMAS_EVENT_FREE_QUANTITY);
+            benefit = champagne.getPrice();
+        }
+        return benefit;
+    }
+
+    public Menu getBenefitGift() {
+        return benefitGift;
+    }
+
+    @Override
+    public int getBenefit() {
+        return benefit;
+    }
+
+    @Override
+    public String getEventName() {
+        return FREE_GIFT_EVENT_TITLE;
+    }
+
+}

리뷰: 저는 증정 이벤트와 할인 이벤트가 갖는 값의 정보도 달라서 따로 분리했습니다.
두 객체를 관리하는 객체를 만들어 관리했습니다.
@@ -0,0 +1,16 @@
+package christmas.message;
+
+public enum ErrorMessages {
+    INVALID_DATE_RANGE("[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요."),
+    INVALID_ORDER_FORMAT("[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.");
+
+    private String message;
+
+    ErrorMessages(String message) {
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}

리뷰: 여기서 그냥 Exception을 throw 하는 것도 좋아 보입니다.
@@ -0,0 +1,41 @@
+package christmas.domain.badge.model;
+
+import java.util.Arrays;
+
+public enum Badge {
+    NONE("없음", 0, 5_000),
+    STAR("별", 5_000, 10_000),
+    TREE("트리", 10_000, 20_000),
+    SANTA("산타", 20_000, Integer.MAX_VALUE);
+
+    private static final Badge[] badges = Badge.values();
+    private final String name;
+    private final int minPrice;
+    private final int maxPrice;
+
+    Badge(String name, int minPrice, int maxPrice) {
+        this.name = name;
+        this.minPrice = minPrice;
+        this.maxPrice = maxPrice;
+    }
+
+    public static Badge from(int price) {
+        return Arrays.stream(badges)
+                .filter(badge -> badge.match(price))
+                .findFirst()
+                .orElse(NONE);
+    }
+
+    private boolean match(int price) {
+        return betweenBadgePrice(price);
+    }
+
+    private boolean betweenBadgePrice(int price) {
+        int absPrice = Math.abs(price);
+        return absPrice >= minPrice && absPrice < maxPrice;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

리뷰: 저와 유사하지만 값의 범위를 정해주시고 탐색하셨군요 !
@@ -0,0 +1,15 @@
+package christmas.domain.benefit.constance;
+
+public interface BenefitConst {
+    String GIFT_DESCRIPTION = "증정 이벤트";
+
+    int D_DAY_INITIAL_PRICE = 1_000;
+    int D_DAY_INCREASE_PRICE = 100;
+    int WEEKDAY_DISCOUNT_PRICE = 2_023;
+    int WEEKEND_DISCOUNT_PRICE = 2_023;
+    int SPECIAL_DISCOUNT_PRICE = 1_000;
+
+    int MIN_PRICE_FOR_BENEFIT = 10_000;
+
+    int MIN_PRICE_FOR_CHAMPAGNE = 120_000;
+}

리뷰: 저도 많은 고민을 했었던 부분인데, enum으로 관리하지 않고 인터페이스 안에서 상수로 관리하시는 이유가 있을까요?
@@ -0,0 +1,32 @@
+package christmas.domain.benefit;
+
+import christmas.domain.benefit.constance.BenefitConst;
+import christmas.domain.benefit.model.Benefit;
+import christmas.domain.benefit.model.Benefits;
+import christmas.domain.benefit.model.discount.DiscountFactories;
+import christmas.domain.benefit.model.gift.GiftFactories;
+import christmas.domain.date.model.PromotionDay;
+import christmas.domain.menu.model.collection.OrderSheet;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BenefitService {
+
+    public Benefits getBenefits(PromotionDay promotionDay, OrderSheet orderSheet) {
+        if (orderSheet.getTotalPrice() < BenefitConst.MIN_PRICE_FOR_BENEFIT) {
+            return new Benefits(List.of());
+        }
+        List<Benefit> benefits = new ArrayList<>();
+        benefits.addAll(getGifts(promotionDay, orderSheet));
+        benefits.addAll(getDiscounts(promotionDay, orderSheet));
+        return new Benefits(benefits);
+    }
+
+    private List<Benefit> getGifts(PromotionDay promotionDay, OrderSheet orderSheet) {
+        return GiftFactories.of(promotionDay, orderSheet);
+    }
+
+    private List<Benefit> getDiscounts(PromotionDay promotionDay, OrderSheet orderSheet) {
+        return DiscountFactories.of(promotionDay, orderSheet);
+    }
+}

리뷰: 코드가 인상적이네요 :)
@@ -0,0 +1,11 @@
+package christmas.domain.benefit.model;
+
+import christmas.domain.date.model.PromotionDay;
+import christmas.domain.menu.model.collection.OrderSheet;
+
+public interface BenefitFactory {
+
+    boolean canApply(PromotionDay promotionDay, OrderSheet orderSheet);
+
+    Benefit generate(PromotionDay promotionDay, OrderSheet orderSheet);
+}

리뷰: generate라는 네이밍은 주로 어떤 의미를 전달하려고 하실 때 사용하시나요?
@@ -0,0 +1,44 @@
+package christmas.domain.benefit.model.discount;
+
+import christmas.domain.benefit.model.Benefit;
+import java.util.Objects;
+
+public class Discount implements Benefit {
+    private final DiscountType type;
+    private final int price;
+
+    Discount(DiscountType type, int price) {
+        this.type = type;
+        this.price = price;
+    }
+    @Override
+    public String getDescription() {
+        return type.getName();
+    }
+
+    @Override
+    public int getBenefitPrice() {
+        return -price;
+    }
+
+    DiscountType getType() {
+        return type;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Discount discount = (Discount) o;
+        return price == discount.price && type == discount.type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, price);
+    }
+}

리뷰: price도 같아야 할까요?
@@ -0,0 +1,59 @@
+package christmas.domain.date.model;
+
+import christmas.domain.date.constance.DateConst;
+import christmas.exception.PromotionException;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.Set;
+
+public class PromotionDay {
+
+    private static final Set<DayOfWeek> weekend = Set.of(DayOfWeek.FRIDAY, DayOfWeek.SATURDAY);
+
+    private static final Set<Integer> specialDay = Set.of(3, 10, 17, 24, 25, 31);
+
+    private static final LocalDate christmas = LocalDate.of(DateConst.YEAR, DateConst.MONTH, 25);
+
+    private final LocalDate localDate;
+
+    private PromotionDay(LocalDate localDate) {
+        this.localDate = localDate;
+    }
+
+    public static PromotionDay from(int date) {
+        validateDate(date);
+        LocalDate localDate = LocalDate.of(DateConst.YEAR, DateConst.MONTH, date);
+        return new PromotionDay(localDate);
+    }
+
+    private static void validateDate(int date) {
+        if (date < DateConst.DECEMBER_DATE_START || date > DateConst.DECEMBER_DATE_END) {
+            throw PromotionException.INVALID_DATE.makeException();
+        }
+    }
+
+    public boolean isWeekend() {
+        return weekend.contains(localDate.getDayOfWeek());
+    }
+
+    public boolean isWeekDay() {
+        return !isWeekend();
+    }
+
+    public boolean isSpecialDay() {
+        int day = this.localDate.getDayOfMonth();
+        return specialDay.contains(day);
+    }
+
+    public int getDDayFromXMax() {
+        return christmas.compareTo(this.localDate);
+    }
+
+    public int getDayFromStart() {
+        return getDate() - 1;
+    }
+
+    public int getDate() {
+        return this.localDate.getDayOfMonth();
+    }
+}

리뷰: Start day가 요구사항에 따라 변경될 수 있다고 생각해서 1을 상수로 관리 해줬었는데, 매직 넘버를 사용하는 것과 상수로 관리하는 것중 어느것이 더 괜찮을까요??
@@ -0,0 +1,41 @@
+package christmas.domain.badge.model;
+
+import java.util.Arrays;
+
+public enum Badge {
+    NONE("없음", 0, 5_000),
+    STAR("별", 5_000, 10_000),
+    TREE("트리", 10_000, 20_000),
+    SANTA("산타", 20_000, Integer.MAX_VALUE);
+
+    private static final Badge[] badges = Badge.values();
+    private final String name;
+    private final int minPrice;
+    private final int maxPrice;
+
+    Badge(String name, int minPrice, int maxPrice) {
+        this.name = name;
+        this.minPrice = minPrice;
+        this.maxPrice = maxPrice;
+    }
+
+    public static Badge from(int price) {
+        return Arrays.stream(badges)
+                .filter(badge -> badge.match(price))
+                .findFirst()
+                .orElse(NONE);
+    }
+
+    private boolean match(int price) {
+        return betweenBadgePrice(price);
+    }
+
+    private boolean betweenBadgePrice(int price) {
+        int absPrice = Math.abs(price);
+        return absPrice >= minPrice && absPrice < maxPrice;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

리뷰: 열거형 값들을 캐싱해두는 전략이 정말 좋은 것 같습니다 :)

추가적으로 `Badge[]`로 배지들을 캐싱해둔 이유도 궁금합니다!
캐싱을 했지만 배열이기 때문에 어쩔 수 없이 순회하는 작업이 필요한데, 어떤 점을 염두해서 캐싱하신건지 궁금해요!!
매 번 `.values()`를 호출하는 것은 비효율적이라 판단하신걸까요?!
@@ -0,0 +1,41 @@
+package christmas.domain.badge.model;
+
+import java.util.Arrays;
+
+public enum Badge {
+    NONE("없음", 0, 5_000),
+    STAR("별", 5_000, 10_000),
+    TREE("트리", 10_000, 20_000),
+    SANTA("산타", 20_000, Integer.MAX_VALUE);
+
+    private static final Badge[] badges = Badge.values();
+    private final String name;
+    private final int minPrice;
+    private final int maxPrice;
+
+    Badge(String name, int minPrice, int maxPrice) {
+        this.name = name;
+        this.minPrice = minPrice;
+        this.maxPrice = maxPrice;
+    }
+
+    public static Badge from(int price) {
+        return Arrays.stream(badges)
+                .filter(badge -> badge.match(price))
+                .findFirst()
+                .orElse(NONE);
+    }
+
+    private boolean match(int price) {
+        return betweenBadgePrice(price);
+    }
+
+    private boolean betweenBadgePrice(int price) {
+        int absPrice = Math.abs(price);
+        return absPrice >= minPrice && absPrice < maxPrice;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

리뷰: 배지의 최솟값과 최댓값을 멤버 변수로 설정해주고, Predicate으로 처리하면 하나의 메서드로 처리할 수 있네요.. 
정말 좋은 코드인 것 같아요! 저는 최댓값을 생각 못해서 if 분기문이 여러개 들어갔는데, 혁수님 코드처럼 개선할 수 있을 것 같습니다 👍
@@ -0,0 +1,41 @@
+package christmas.domain.badge.model;
+
+import java.util.Arrays;
+
+public enum Badge {
+    NONE("없음", 0, 5_000),
+    STAR("별", 5_000, 10_000),
+    TREE("트리", 10_000, 20_000),
+    SANTA("산타", 20_000, Integer.MAX_VALUE);
+
+    private static final Badge[] badges = Badge.values();
+    private final String name;
+    private final int minPrice;
+    private final int maxPrice;
+
+    Badge(String name, int minPrice, int maxPrice) {
+        this.name = name;
+        this.minPrice = minPrice;
+        this.maxPrice = maxPrice;
+    }
+
+    public static Badge from(int price) {
+        return Arrays.stream(badges)
+                .filter(badge -> badge.match(price))
+                .findFirst()
+                .orElse(NONE);
+    }
+
+    private boolean match(int price) {
+        return betweenBadgePrice(price);
+    }
+
+    private boolean betweenBadgePrice(int price) {
+        int absPrice = Math.abs(price);
+        return absPrice >= minPrice && absPrice < maxPrice;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

리뷰:  NONE으로 없음 처리하신 것도 좋은 접근 방법이라 생각합니다 :)

`Optional`도 고려해보시면 좋을 것 같아요!
그렇게 되면 from 메서드가 이런식으로 바뀔 것 같습니다.

```java
public static Optional<Badge> from(int price) {
   Optional<Badge> result = Arrays.stream(badges)
            .filter(badge -> badge.match(price))
            .findFirst();

    if(result.isPresent()) {
        return Optional.get();
    }
    return Optional.empty():
}
```

정확히 어느 지점에서 Optional을 사용해야 하는지, Enum 내부에서 Optional을 사용해도 괜찮을지 저도 아직 명확한 기준은 없지만 **없을 수도 있다 라는 비즈니스 요구사항을 코드로 명시** 할 수 있는 측면에서 아주 강력한 장점이 있는 것 같아요 :)

그냥 참고 정도만 하시면 좋을 것 같습니다 😊
@@ -0,0 +1,32 @@
+package christmas.domain.benefit;
+
+import christmas.domain.benefit.constance.BenefitConst;
+import christmas.domain.benefit.model.Benefit;
+import christmas.domain.benefit.model.Benefits;
+import christmas.domain.benefit.model.discount.DiscountFactories;
+import christmas.domain.benefit.model.gift.GiftFactories;
+import christmas.domain.date.model.PromotionDay;
+import christmas.domain.menu.model.collection.OrderSheet;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BenefitService {
+
+    public Benefits getBenefits(PromotionDay promotionDay, OrderSheet orderSheet) {
+        if (orderSheet.getTotalPrice() < BenefitConst.MIN_PRICE_FOR_BENEFIT) {
+            return new Benefits(List.of());
+        }
+        List<Benefit> benefits = new ArrayList<>();
+        benefits.addAll(getGifts(promotionDay, orderSheet));
+        benefits.addAll(getDiscounts(promotionDay, orderSheet));
+        return new Benefits(benefits);
+    }
+
+    private List<Benefit> getGifts(PromotionDay promotionDay, OrderSheet orderSheet) {
+        return GiftFactories.of(promotionDay, orderSheet);
+    }
+
+    private List<Benefit> getDiscounts(PromotionDay promotionDay, OrderSheet orderSheet) {
+        return DiscountFactories.of(promotionDay, orderSheet);
+    }
+}

리뷰: if 조건문은 개인적으로 메서드 분리하는 것도 괜찮을 것 같습니다!

그 이유는 20, 21 line에선 메서드 호출을 통해 로직을 처리하고 있기 때문에, 동일한 depth로 통일시켜 주신다면 조금 더 읽기 좋은 코드가 될 수 있을 것 같아요 :)
@@ -0,0 +1,19 @@
+package christmas.domain.benefit.model.discount;
+
+public enum DiscountType {
+    D_DAY("크리스마스 디데이 할인"), //12월 25일을 지나지 않은 경우 //날짜에 따라 100원씩 증가
+    WEEKDAY("평일 할인"), //평일인 경우 // 디저트 메뉴 * 금액
+    WEEKEND("주말 할인"), //주말인 경우 // 메인 메뉴 * 금액
+    SPECIAL("특별 할인") //스페셜 데이인 경우 // 무조건 1000원
+    ;
+
+    private final String name;
+
+    DiscountType(String name) {
+        this.name = name;
+    }
+
+    String getName() {
+        return name;
+    }
+}

리뷰: 저는 `할인 유형(DiscountType)`, `할인 유형에 대한 날짜`, `할인 유형에 대한 할인 계산 로직`
위의 3가지는 DiscountType Enum과 **아주 긴밀하게 연관되어 있는 상수**라 생각이 들었습니다!

그래서 이런식으로 DiscountType을 구현했습니다.
```java
public enum DiscountType {
    CHRISTMAS("크리스마스 디데이 할인", EventDayCalculator.christmasDay(), orderDay -> {
        final int basicDiscountAmount = 1000;
        final int discountAmountUnit = 100;
        return (basicDiscountAmount + discountAmountUnit * (orderDay - 1)) * -1;
    }),
    WEEKDAY("평일 할인", EventDayCalculator.weekDay(), dessertCount -> {
        final int discountAmountUnit = 2023;
        return (discountAmountUnit * dessertCount) * -1;
    }),
    WEEKEND("주말 할인", EventDayCalculator.weekEnd(), mainCourseCount -> {
        final int discountAmountUnit = 2023;
        return (discountAmountUnit * mainCourseCount) * -1;
    }),
    SPECIAL("특별 할인", EventDayCalculator.specialDay(), anything -> {
        return -1000;
    });
    
// something
```

이렇게 처리를 하니 **할인 유형이라는 상태** 와 **계산 로직이라는 행위** 를 밀집시킬 수 있는 장점이 있었습니다.
할인 유형이라는 상태와 계산이라는 행위가 산개되어 있었는데, 밀집시키니 굉장히 관리하기 편하다는 생각이 들더라구요.

이런 방법으로 짱수님 코드를 리팩터링 한다면, `BenefitFactory`의 `isApply()`를 `DiscountType`이 내부적으로 처리할 수 있을 것 같고, 할인 계산을 수행하라는 명령 처리 역할을 담당하는 클래스만 구현해주면 될 것 같습니다. 그럼 코드 복잡도가 조금 줄어들지 않을까..? 라는 개인 생각입니다 ㅎㅎㅎ

이런 방법은 어떠신지 짱수님의 생각을 여쭙고 싶어요 :)
@@ -0,0 +1,52 @@
+package christmas.domain.menu.model;
+
+import christmas.exception.PromotionException;
+import java.util.Arrays;
+
+public enum Menu {
+
+    MUSHROOM_SOUP(MenuType.APPETIZER, "양송이수프", 6_000),
+    TAPAS(MenuType.APPETIZER, "타파스", 5_500),
+    CAESAR_SALAD(MenuType.APPETIZER, "시저샐러드", 8_000),
+
+    T_BONE_STEAK(MenuType.MAIN, "티본스테이크", 55_000),
+    BARBCUE_RIBS(MenuType.MAIN, "바비큐립", 54_000),
+    SEAFOOD_PASTA(MenuType.MAIN, "해산물파스타", 35_000),
+    CHRISTMAS_PASTA(MenuType.MAIN, "크리스마스파스타", 25_000),
+
+    CHOCOLATE_CAKE(MenuType.DESSERT, "초코케이크", 15_000),
+    ICE_CREAM(MenuType.DESSERT, "아이스크림", 5_000),
+
+    ZERO_COKE(MenuType.DRINK, "제로콜라", 3_000),
+    RED_WINE(MenuType.DRINK, "레드와인", 60_000),
+    CHAMPAGNE(MenuType.DRINK, "샴페인", 25_000);
+
+    private final MenuType type;
+    private final String name;
+    private final int price;
+
+    Menu(MenuType type, String name, int price) {
+        this.type = type;
+        this.name = name;
+        this.price = price;
+    }
+
+    public static Menu from(String menuName) {
+        return Arrays.stream(Menu.values())
+                .filter(menu -> menu.name.equals(menuName))
+                .findFirst()
+                .orElseThrow(PromotionException.INVALID_ORDER::makeException);
+    }
+
+    public boolean isTypeOf(MenuType type){
+        return this.type.equals(type);
+    }
+
+    String getName() {
+        return name;
+    }
+
+    int getPrice() {
+        return price;
+    }
+}

리뷰: Badge Enum에서 `.values()`를 `배열로 캐싱`하신 것과 같은 맥락으로 `HashMap으로 캐싱` 해두는 방법도 추천드립니다.

Enum에선 static 블록을 추가할 수 있는데요, 요런식으로 구현할 수 있습니다!
```java
public enum Menu {
    // 열거형 상수들 ...

    private static final Map<String, Menu> cachedMenu;

    static {
        for (Menu menu : values()) {
            cachedMenu.put(menu.getName(), menu);
        }
    }
}
```
이렇게 Map으로 캐싱해두면, 매 번 열거형 상수들을 순회하지 않아도 되는 장점이 있습니다 :)

하지만 NPE가 발생할 위험이 있기 때문에, Optional을 사용해보시는 것도 고려해보시면 좋을 것 같습니다.
그렇게 하면

```java
public static Optional<Menu> from(String menuName) {
        return Optional.ofNullalbe(cachedMenu.get(menuName);
}
```
위와 같이 리팩터링 해볼 수 있을 것 같아요 :)
@@ -0,0 +1,37 @@
+package christmas.domain.menu.model.collection;
+
+import christmas.domain.menu.model.MenuAndCount;
+import christmas.domain.menu.model.MenuType;
+import java.util.Collections;
+import java.util.List;
+
+/** 주문 확인을 위한 불변 일급 컬렉션 입니다. */
+public class OrderSheet {
+    private final List<MenuAndCount> orderSheet;
+
+    public OrderSheet(List<MenuAndCount> orderSheet) {
+        this.orderSheet = orderSheet;
+    }
+
+    public List<MenuAndCount> getOrderSheet() {
+        return Collections.unmodifiableList(orderSheet);
+    }
+
+    public int getTotalPrice() {
+        return orderSheet.stream()
+                .mapToInt(MenuAndCount::getTotalPrice)
+                .sum();
+    }
+
+    public boolean hasMenuOfType(MenuType type) {
+        return orderSheet.stream()
+                .anyMatch(menuAndCount -> menuAndCount.isTypeOf(type));
+    }
+
+    public int getCountOfMenuType(MenuType type) {
+        return orderSheet.stream()
+                .filter(menuAndCount -> menuAndCount.isTypeOf(type))
+                .mapToInt(MenuAndCount::getCount)
+                .sum();
+    }
+}

리뷰: `OrderSheet`와 `Orders`를 분리하신 이유가 궁금합니다 !
@@ -0,0 +1,10 @@
+package christmas.domain.badge;
+
+import christmas.domain.badge.model.Badge;
+import christmas.domain.benefit.model.Benefits;
+
+public class BadgeService {
+    public Badge getBadge(Benefits benefits) {
+        return Badge.from(benefits.getTotalPrice());
+    }
+}

리뷰: 패키지 구조 전반에 대한 짱수님의 의견이 궁금해서 질문 드려요!
짱수님의 경우 아래와 같은 구조로 진행하셨더라구요!
```
domain
  ㄴ badge
    ㄴ 관련 domain 객체
    ㄴ badgeService
  ㄴ benefit
    ㄴ 관련 domain 객체
    ㄴ benefitService
```

저는 이런 방식으로 구현했는데요!
```
domain
  ㄴ badge
    ㄴ 관련 domain 객체
  ㄴ benefit
    ㄴ 관련 domain 객체

service
  ㄴ badgeService
  ㄴ benefitService
```

처음 제 생각은 `domain layer`는 domain 관련 객체들만 모아 놓는 곳이라고 생각하여 위와 같이 구현했습니다. `Service layer`는 적절한 domain에게 명령을 요청하고 반환하는 구조입니다.

하지만 짱수님의 패키지 구조를 보니 `응집도가 더 높아질 수 있겠구나` 라는 장점이 있는 것 같아요!
프로덕트 진행하며 패키지 구조를 유기적으로 바꾸어 나가는게 중요하다는 생각이 있는데, 짱수님은 어떤 기준으로 패키지 구조를 위와 같이 구조화하신걸까요?!