ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,74 @@
+package christmas.domain;
+
+import christmas.constant.DiscountConstants;
+import christmas.constant.DiscountPolicyName;
+import christmas.domain.discount.ChristmasDailyDiscountPolicy;
+import christmas.domain.discount.DiscountPolicy;
+import christmas.domain.discount.GiftEventPolicy;
+import christmas.domain.discount.SpecialDiscountPolicy;
+import christmas.domain.discount.WeekdayDiscountPolicy;
+import christmas.domain.discount.WeekendDiscountPolicy;
+import christmas.domain.menu.Menu;
+import christmas.service.dto.OrderDto;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+public class ChristmasEvent {
+    private static final int MINIMUM_AMOUNT_FOR_DISCOUNT = 10000;
+    private static final int MINIMUM_AMOUNT_FOR_GIFT = 120000;
+    private final List<DiscountPolicy> discountPolicies;
+    private final GiftEventPolicy giftEventPolicy;
+    private final Map<Menu, Integer> giftMenu;
+
+    public ChristmasEvent() {
+        this.discountPolicies = List.of(
+                new ChristmasDailyDiscountPolicy(),
+                new WeekdayDiscountPolicy(),
+                new WeekendDiscountPolicy(),
+                new SpecialDiscountPolicy());
+        this.giftEventPolicy = new GiftEventPolicy();
+        this.giftMenu = new EnumMap<>(Menu.class);
+    }
+
+    public Map<DiscountPolicyName, Integer> calculateBenefitDetails(final OrderDto order) {
+        Map<DiscountPolicyName, Integer> benefitDetail = new EnumMap<>(DiscountPolicyName.class);
+        if (order.getTotalPrice() >= MINIMUM_AMOUNT_FOR_DISCOUNT) {
+            benefitDetail = applyDiscountPolicies(order);
+            addGift(order, benefitDetail);
+        }
+        return benefitDetail;
+    }
+
+    public int calculateTotalDiscount(final OrderDto order) {
+        Map<DiscountPolicyName, Integer> discountDetail = calculateBenefitDetails(order);
+        return discountDetail.values()
+                .stream()
+                .mapToInt(Integer::intValue)
+                .sum();
+    }
+
+    private void addGift(final OrderDto order, final Map<DiscountPolicyName, Integer> benefitDetail) {
+        if (order.getTotalPrice() >= MINIMUM_AMOUNT_FOR_GIFT) {
+            int giftPrice = giftEventPolicy.discountGiftPrice(order);
+            benefitDetail.put(DiscountPolicyName.GIFT_EVENT, giftPrice);
+            giftMenu.put(Menu.CHAMPAGNE, 1);
+        }
+    }
+
+    private Map<DiscountPolicyName, Integer> applyDiscountPolicies(final OrderDto order) {
+        Map<DiscountPolicyName, Integer> discountDetail = new EnumMap<>(DiscountPolicyName.class);
+        for (DiscountPolicy discountPolicy : discountPolicies) {
+            int discountAmount = discountPolicy.discount(order);
+            if (discountAmount != DiscountConstants.NO_DISCOUNT) {
+                discountDetail.put(discountPolicy.getDiscountPolicyName(), discountAmount);
+            }
+        }
+        return discountDetail;
+    }
+
+    public Map<Menu, Integer> getGiftMenu(final OrderDto order) {
+        calculateBenefitDetails(order);
+        return giftMenu;
+    }
+}
\ No newline at end of file

리뷰: 증정 메뉴에 대해서 확장성이 보여서 좋은 거 같아요! 👍
@@ -0,0 +1,15 @@
+package christmas.model.strategy;
+
+import christmas.model.constant.EventBadge;
+
+public class WootecoBadgeStrategy implements BadgeStrategy {
+    @Override
+    public EventBadge calculateBadgeGrade(int promotionAmount) {
+        for (EventBadge badge : EventBadge.values()){
+            if (promotionAmount >= badge.getBaseAmount()){
+                return badge;
+            }
+        }
+        return EventBadge.NOTHING;
+    }
+}

리뷰: 현재는 EventBadge가 기준 금액이 높은 순서대로 구현되어 있지만 만일 다른 사람이 순서를 무시하고 뱃지를 추가하는 경우도 고려하시면 좋을 것 같습니다.
@@ -0,0 +1,29 @@
+package christmas.view;
+
+import camp.nextstep.edu.missionutils.Console;
+import christmas.util.Parser;
+import christmas.validator.MenuValidator;
+
+public class InputView {
+
+    public static void printGreetingMessage() {
+        System.out.println(ViewMessage.GREETING.getMessage());
+    }
+
+
+    public static  int readDate() {
+        System.out.println(ViewMessage.DATE.getMessage());
+
+        String input = Console.readLine();
+        return Parser.parseDate(input);
+    }
+
+
+    public static String readMenu() {
+        System.out.println(ViewMessage.MENU.getMessage());
+        String input = Console.readLine();
+        MenuValidator.validateMenuInputFormat(input);
+        return input;
+    }
+
+}

리뷰: static 메소드밖에 없는 유틸리티 클래스의 경우, 인스턴스화가 필요 없기 때문에 생성자를 private으로 하여 인스턴스화를 막아주면 더 좋을 것 같습니다

https://rules.sonarsource.com/java/RSPEC-1118/
@@ -0,0 +1,44 @@
+package christmas.model.constant;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public enum DateConstant {
+    MIN_DATE(1),
+    MAX_DATE(31),
+    XMAS(25);
+
+    public static final Set<Integer> WEEKDAYS = new HashSet<>();
+    public static final Set<Integer> WEEKENDS = new HashSet<>();
+    public static final Set<Integer> SPECIAL_DAYS = new HashSet<>(Arrays.asList(
+            3, 10, 17, 24, 25, 31
+    ));
+
+    static {
+        LocalDate start = LocalDate.of(2023, 12, MIN_DATE.date);
+        LocalDate end = LocalDate.of(2023, 12, MAX_DATE.date);
+
+        for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
+            DayOfWeek day = date.getDayOfWeek();
+            if (day == DayOfWeek.SATURDAY || day == DayOfWeek.FRIDAY) {
+                WEEKENDS.add(date.getDayOfMonth());
+                continue;
+            }
+            WEEKDAYS.add(date.getDayOfMonth());
+        }
+    }
+
+    private final int date;
+
+    DateConstant(int date) {
+        this.date = date;
+    }
+
+    public int getDate() {
+        return date;
+    }
+
+}

리뷰: ```suggestion
    public static final Set<Integer> SPECIAL_DAYS = Set.of(3, 10, 17, 24, 25, 31);
```
`Set.of` 를 활용해서 더 코드를 간결하게 만들 수 있을 것 같아요.
@@ -0,0 +1,44 @@
+package christmas.model.constant;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public enum DateConstant {
+    MIN_DATE(1),
+    MAX_DATE(31),
+    XMAS(25);
+
+    public static final Set<Integer> WEEKDAYS = new HashSet<>();
+    public static final Set<Integer> WEEKENDS = new HashSet<>();
+    public static final Set<Integer> SPECIAL_DAYS = new HashSet<>(Arrays.asList(
+            3, 10, 17, 24, 25, 31
+    ));
+
+    static {
+        LocalDate start = LocalDate.of(2023, 12, MIN_DATE.date);
+        LocalDate end = LocalDate.of(2023, 12, MAX_DATE.date);
+
+        for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
+            DayOfWeek day = date.getDayOfWeek();
+            if (day == DayOfWeek.SATURDAY || day == DayOfWeek.FRIDAY) {
+                WEEKENDS.add(date.getDayOfMonth());
+                continue;
+            }
+            WEEKDAYS.add(date.getDayOfMonth());
+        }
+    }
+
+    private final int date;
+
+    DateConstant(int date) {
+        this.date = date;
+    }
+
+    public int getDate() {
+        return date;
+    }
+
+}

리뷰: SPECIAL_DAYS가 public으로 열려있으면서 불변이 아니므로 (HashMap) 외부에서 값을 수정하는 해킹이 가능할 것으로 보입니다!


예시입니다
```java
DateConstant.SPECIAL_DAYS.add(-1);
```
@@ -0,0 +1,40 @@
+package christmas.exception;
+
+public class CustomException extends IllegalArgumentException {
+    private static final String MENU_ERROR_MESSAGE = "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.";
+    private static final String DATE_ERROR_MESSAGE = "[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.";
+    public CustomException(String s) {
+        super(s);
+    }
+
+    @Override
+    public synchronized  Throwable fillInStackTrace(){
+        return this;
+    }
+
+    public static final CustomException DATE_NOT_INTEGER_EXCEPTION =
+            new CustomException(DATE_ERROR_MESSAGE);
+
+    public static final CustomException DATE_RANGE_EXCEPTION =
+            new CustomException(DATE_ERROR_MESSAGE);
+
+    public static final CustomException DUPLICATE_MENU_EXCEPTION =
+            new CustomException(MENU_ERROR_MESSAGE);
+
+    public static final CustomException MENU_COUNT_ZERO_EXCEPTION =
+            new CustomException(MENU_ERROR_MESSAGE);
+
+    public static final CustomException MENU_FORMAT_EXCEPTION =
+            new CustomException(MENU_ERROR_MESSAGE);
+
+    public static final CustomException MENU_NOT_FOUND_EXCEPTION =
+            new CustomException(MENU_ERROR_MESSAGE);
+
+    public static final CustomException ONLY_DRINK_EXCEPTION =
+            new CustomException(MENU_ERROR_MESSAGE);
+
+    public static final CustomException MENU_AMOUNT_OVER_LIMIT_EXCEPTION =
+            new CustomException(MENU_ERROR_MESSAGE);
+
+
+}

리뷰: 커스텀 익셉션이라는 이름보다, 조금 더 예외의 이유 등의 의미가 드러나는 이름이면 좋을 것 같습니다
@@ -0,0 +1,56 @@
+package christmas.model.constant;
+
+import java.util.Arrays;
+
+public enum Menu {
+    SOUP("애피타이저", "양송이수프", 6000),
+    TAPAS("애피타이저", "타파스", 5500),
+    SALAD("애피타이저", "시저샐러드", 8000),
+    STEAK("메인", "티본스테이크", 55000),
+    BBQ("메인", "바비큐립", 54000),
+    SEAFOOD_PASTA("메인", "해산물파스타", 35000),
+    XMAS_PASTA("메인", "크리스마스파스타", 25000),
+    CHOCO_CAKE("디저트", "초코케이크", 15000),
+    ICECREAM("디저트", "아이스크림", 5000),
+    ZEROCOKE("음료", "제로콜라", 3000),
+    REDWINE("음료", "레드와인", 60000),
+    CHAMPAGNE("음료", "샴페인", 25000);
+
+
+    private final String category;
+    private final String name;
+    private final int price;
+
+    Menu(String category, String name, int price) {
+        this.category = category;
+        this.name = name;
+        this.price = price;
+    }
+
+    public static Menu of(String name) {
+        return Arrays.stream(Menu.values())
+                .filter(menu -> menu.getName().equals(name))
+                .findFirst()
+                .orElse(null);
+    }
+
+    public boolean isMainCategory() {
+        return "메인".equals(this.category);
+    }
+
+    public boolean isDessertCategory() {
+        return "디저트".equals(this.category);
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+}

리뷰: String 리터럴로 "메인"과 같이 작성하는 것보다 메뉴의 카테고리도 enum으로 관리하면 조금 더 용이하지 않을까요?
@@ -0,0 +1,52 @@
+package christmas.model;
+
+import static christmas.exception.CustomException.DATE_RANGE_EXCEPTION;
+import static christmas.model.constant.DateConstant.SPECIAL_DAYS;
+import static christmas.model.constant.DateConstant.WEEKDAYS;
+import static christmas.model.constant.DateConstant.WEEKENDS;
+import static christmas.model.constant.DateConstant.XMAS;
+
+import christmas.model.constant.DateConstant;
+
+public class Date {
+    private final int MIN_DATE = DateConstant.MIN_DATE.getDate();
+    private final int MAX_DATE = DateConstant.MAX_DATE.getDate();
+    private final int date;
+
+    private Date(int date) {
+        validateDateRange(date);
+        this.date = date;
+    }
+
+    public static Date of(int date) {
+        return new Date(date);
+    }
+
+    public int getValue() {
+        return date;
+    }
+
+    private void validateDateRange(int date) {
+        if (date < MIN_DATE || date > MAX_DATE) {
+            throw DATE_RANGE_EXCEPTION;
+        }
+    }
+
+    public boolean isWeekday() {
+        return WEEKDAYS.contains(date);
+    }
+
+    public boolean isWeekend() {
+        return WEEKENDS.contains(date);
+    }
+
+    public boolean isBeforeXmas() {
+        return XMAS.getDate() >= date;
+    }
+
+    public boolean isSpecialDay() {
+        return SPECIAL_DAYS.contains(date);
+    }
+
+
+}

리뷰: 자바의 빌트인 객체인 [Date](https://docs.oracle.com/javase/8/docs/api/java/util/Date.html)와 혼동의 여지가 있을 것 같습니다..!

(일단 전 헷갈렸어요 😭)
@@ -0,0 +1,22 @@
+package christmas.model.constant;
+
+public enum PromotionConstant {
+
+    CHRISTMAS_DISCOUNT("크리스마스 디데이 할인"),
+    WEEKDAY_DISCOUNT("평일 할인"),
+    WEEKEND_DISCOUNT("주말 할인"),
+    SPECIAL_DISCOUNT("특별 할인"),
+    GIFT_EVENT("증정 이벤트");
+
+    private String name;
+
+    PromotionConstant(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+
+}

리뷰: private final로 만들 수 있을 것 같아요
@@ -0,0 +1,75 @@
+package christmas.model.strategy;
+
+import static christmas.model.constant.DiscountConstant.CHRISTMAS_START_DISCOUNT;
+import static christmas.model.constant.DiscountConstant.DISCOUNT_INCREMENT;
+import static christmas.model.constant.DiscountConstant.FIXED_DISCOUNT;
+import static christmas.model.constant.DiscountConstant.GIFT_EVENT_THRESHOLD;
+import static christmas.model.constant.DiscountConstant.NO_DISCOUNT;
+import static christmas.model.constant.DiscountConstant.SPECIAL_DAY_DISCOUNT;
+
+import christmas.model.Date;
+import christmas.model.constant.Menu;
+import christmas.model.MenuCount;
+
+public class WootecoDiscountStrategy implements DiscountStrategy {
+
+    @Override
+    public int christmasDiscount(Date date) {
+        if (date.isBeforeXmas()) {
+            return (CHRISTMAS_START_DISCOUNT.getAmount() + (date.getValue() - 1)
+                    * DISCOUNT_INCREMENT.getAmount());
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int weekendDiscount(MenuCount menuCount, Date date) {
+        if (date.isWeekend()) {
+            return calculateWeekendDiscountAmount(menuCount);
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int weekdayDiscount(MenuCount menuCount, Date date) {
+        if (date.isWeekday()) {
+            return calculateWeekdayDiscountAmount(menuCount);
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int specialDayDiscount(Date date) {
+        if (date.isSpecialDay()) {
+            return SPECIAL_DAY_DISCOUNT.getAmount();
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int giftEventDiscount(MenuCount menuCount) {
+        if (menuCount.calculateTotalAmount() >= GIFT_EVENT_THRESHOLD.getAmount()) {
+            return Menu.CHAMPAGNE.getPrice();
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+
+    private int calculateWeekendDiscountAmount(MenuCount menuCount) {
+        return menuCount.getValue()
+                .entrySet()
+                .stream()
+                .filter(entry -> entry.getKey().isMainCategory())
+                .mapToInt(entry -> FIXED_DISCOUNT.getAmount() * entry.getValue())
+                .sum();
+    }
+
+    private int calculateWeekdayDiscountAmount(MenuCount menuCount) {
+        return menuCount.getValue()
+                .entrySet()
+                .stream()
+                .filter(entry -> entry.getKey().isDessertCategory())
+                .mapToInt(entry -> FIXED_DISCOUNT.getAmount() * entry.getValue())
+                .sum();
+    }
+}

리뷰: 여러 할인 전략을 한 클래스에 몰아넣기보다는 각각 클래스로 분류할 수 있을 것 같아요

+ 그리고 그렇게 나누어서, 우테코 할인 전략이라는 이름보다는 무슨 할인 전략인지에 대한 의미가 드러나는 이름이면 좋을 것 같아요
@@ -0,0 +1,75 @@
+package christmas.model.strategy;
+
+import static christmas.model.constant.DiscountConstant.CHRISTMAS_START_DISCOUNT;
+import static christmas.model.constant.DiscountConstant.DISCOUNT_INCREMENT;
+import static christmas.model.constant.DiscountConstant.FIXED_DISCOUNT;
+import static christmas.model.constant.DiscountConstant.GIFT_EVENT_THRESHOLD;
+import static christmas.model.constant.DiscountConstant.NO_DISCOUNT;
+import static christmas.model.constant.DiscountConstant.SPECIAL_DAY_DISCOUNT;
+
+import christmas.model.Date;
+import christmas.model.constant.Menu;
+import christmas.model.MenuCount;
+
+public class WootecoDiscountStrategy implements DiscountStrategy {
+
+    @Override
+    public int christmasDiscount(Date date) {
+        if (date.isBeforeXmas()) {
+            return (CHRISTMAS_START_DISCOUNT.getAmount() + (date.getValue() - 1)
+                    * DISCOUNT_INCREMENT.getAmount());
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int weekendDiscount(MenuCount menuCount, Date date) {
+        if (date.isWeekend()) {
+            return calculateWeekendDiscountAmount(menuCount);
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int weekdayDiscount(MenuCount menuCount, Date date) {
+        if (date.isWeekday()) {
+            return calculateWeekdayDiscountAmount(menuCount);
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int specialDayDiscount(Date date) {
+        if (date.isSpecialDay()) {
+            return SPECIAL_DAY_DISCOUNT.getAmount();
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int giftEventDiscount(MenuCount menuCount) {
+        if (menuCount.calculateTotalAmount() >= GIFT_EVENT_THRESHOLD.getAmount()) {
+            return Menu.CHAMPAGNE.getPrice();
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+
+    private int calculateWeekendDiscountAmount(MenuCount menuCount) {
+        return menuCount.getValue()
+                .entrySet()
+                .stream()
+                .filter(entry -> entry.getKey().isMainCategory())
+                .mapToInt(entry -> FIXED_DISCOUNT.getAmount() * entry.getValue())
+                .sum();
+    }
+
+    private int calculateWeekdayDiscountAmount(MenuCount menuCount) {
+        return menuCount.getValue()
+                .entrySet()
+                .stream()
+                .filter(entry -> entry.getKey().isDessertCategory())
+                .mapToInt(entry -> FIXED_DISCOUNT.getAmount() * entry.getValue())
+                .sum();
+    }
+}

리뷰: 크리스마스 할인 전략에서 변동이 생기게 된다면 (예: 시작할인액이 1000원에서 2000원으로 변경), 해당 변경사항에 대한 여파가 할인 전략만 여파를 미쳐야 할 것 같은데 enum인 DiscountConstant에 영향을 미치게 돼요.

마찬가지로 주말 할인 전략에도 변동이 생긴다면 DiscountConstant이 바뀌어야 하는데, 이는 `클래스가 변경될 이유는 한 가지여야 한다`라는 SRP원칙을 위반하는 것 같아요
@@ -0,0 +1,75 @@
+package christmas.model.strategy;
+
+import static christmas.model.constant.DiscountConstant.CHRISTMAS_START_DISCOUNT;
+import static christmas.model.constant.DiscountConstant.DISCOUNT_INCREMENT;
+import static christmas.model.constant.DiscountConstant.FIXED_DISCOUNT;
+import static christmas.model.constant.DiscountConstant.GIFT_EVENT_THRESHOLD;
+import static christmas.model.constant.DiscountConstant.NO_DISCOUNT;
+import static christmas.model.constant.DiscountConstant.SPECIAL_DAY_DISCOUNT;
+
+import christmas.model.Date;
+import christmas.model.constant.Menu;
+import christmas.model.MenuCount;
+
+public class WootecoDiscountStrategy implements DiscountStrategy {
+
+    @Override
+    public int christmasDiscount(Date date) {
+        if (date.isBeforeXmas()) {
+            return (CHRISTMAS_START_DISCOUNT.getAmount() + (date.getValue() - 1)
+                    * DISCOUNT_INCREMENT.getAmount());
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int weekendDiscount(MenuCount menuCount, Date date) {
+        if (date.isWeekend()) {
+            return calculateWeekendDiscountAmount(menuCount);
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int weekdayDiscount(MenuCount menuCount, Date date) {
+        if (date.isWeekday()) {
+            return calculateWeekdayDiscountAmount(menuCount);
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int specialDayDiscount(Date date) {
+        if (date.isSpecialDay()) {
+            return SPECIAL_DAY_DISCOUNT.getAmount();
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+    @Override
+    public int giftEventDiscount(MenuCount menuCount) {
+        if (menuCount.calculateTotalAmount() >= GIFT_EVENT_THRESHOLD.getAmount()) {
+            return Menu.CHAMPAGNE.getPrice();
+        }
+        return NO_DISCOUNT.getAmount();
+    }
+
+
+    private int calculateWeekendDiscountAmount(MenuCount menuCount) {
+        return menuCount.getValue()
+                .entrySet()
+                .stream()
+                .filter(entry -> entry.getKey().isMainCategory())
+                .mapToInt(entry -> FIXED_DISCOUNT.getAmount() * entry.getValue())
+                .sum();
+    }
+
+    private int calculateWeekdayDiscountAmount(MenuCount menuCount) {
+        return menuCount.getValue()
+                .entrySet()
+                .stream()
+                .filter(entry -> entry.getKey().isDessertCategory())
+                .mapToInt(entry -> FIXED_DISCOUNT.getAmount() * entry.getValue())
+                .sum();
+    }
+}

리뷰: 그런 이유에서, CHRISTMAS_START_DISCOUNT는 enum으로 public하게 아무 클래스에서나 공개될 필요가 없이, 해당 할인 정책 클래스에서 `private static final`과 같이 관리하면 더 좋을 것 같아요
(할인 정책이 바뀌더라도 할인 정책 클래스만 영향을 받게 되겠죠)
@@ -0,0 +1,52 @@
+package christmas.model;
+
+import static christmas.exception.CustomException.DATE_RANGE_EXCEPTION;
+import static christmas.model.constant.DateConstant.SPECIAL_DAYS;
+import static christmas.model.constant.DateConstant.WEEKDAYS;
+import static christmas.model.constant.DateConstant.WEEKENDS;
+import static christmas.model.constant.DateConstant.XMAS;
+
+import christmas.model.constant.DateConstant;
+
+public class Date {
+    private final int MIN_DATE = DateConstant.MIN_DATE.getDate();
+    private final int MAX_DATE = DateConstant.MAX_DATE.getDate();
+    private final int date;
+
+    private Date(int date) {
+        validateDateRange(date);
+        this.date = date;
+    }
+
+    public static Date of(int date) {
+        return new Date(date);
+    }
+
+    public int getValue() {
+        return date;
+    }
+
+    private void validateDateRange(int date) {
+        if (date < MIN_DATE || date > MAX_DATE) {
+            throw DATE_RANGE_EXCEPTION;
+        }
+    }
+
+    public boolean isWeekday() {
+        return WEEKDAYS.contains(date);
+    }
+
+    public boolean isWeekend() {
+        return WEEKENDS.contains(date);
+    }
+
+    public boolean isBeforeXmas() {
+        return XMAS.getDate() >= date;
+    }
+
+    public boolean isSpecialDay() {
+        return SPECIAL_DAYS.contains(date);
+    }
+
+
+}

리뷰: 주말인지 평일인지를 체크하는 로직을 `DateConstant`에 몰기보다, Date 클래스에서 처리하면 어떨까요?
상수 클래스에 로직이 들어있는 것은 이상한 것 같다는 생각이 들어요
@@ -0,0 +1,44 @@
+package christmas.model.constant;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public enum DateConstant {
+    MIN_DATE(1),
+    MAX_DATE(31),
+    XMAS(25);
+
+    public static final Set<Integer> WEEKDAYS = new HashSet<>();
+    public static final Set<Integer> WEEKENDS = new HashSet<>();
+    public static final Set<Integer> SPECIAL_DAYS = new HashSet<>(Arrays.asList(
+            3, 10, 17, 24, 25, 31
+    ));
+
+    static {
+        LocalDate start = LocalDate.of(2023, 12, MIN_DATE.date);
+        LocalDate end = LocalDate.of(2023, 12, MAX_DATE.date);
+
+        for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
+            DayOfWeek day = date.getDayOfWeek();
+            if (day == DayOfWeek.SATURDAY || day == DayOfWeek.FRIDAY) {
+                WEEKENDS.add(date.getDayOfMonth());
+                continue;
+            }
+            WEEKDAYS.add(date.getDayOfMonth());
+        }
+    }
+
+    private final int date;
+
+    DateConstant(int date) {
+        this.date = date;
+    }
+
+    public int getDate() {
+        return date;
+    }
+
+}

리뷰: 리터럴 값도 컨벤션에 맞게 상수 처리를 하면 될 것 같습니다