ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -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();
+    }
+}

리뷰: 하드코딩하지 않고도 LocalDate를 활용하면 되는군요.. 배워갑니다 👍👍
@@ -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());
+    }
+}

리뷰: 저도 이 부분이 궁금해서 커멘트 남기고 갑니다! 저도 june님과 같이 구현했는데, 읽는 입장에서 보니 도메인 코드를 읽고 서비스 코드를 읽으니 이해가 훨씬 수월하더라구요!
@@ -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;
+    }
+}

리뷰: orElse에 상수와 관련없는 값이 들어가는 경우도 많이 봤는데, 이 코드에선 orElse마저 역할이 분명해서 너무 좋은 것 같습니다!
@@ -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,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문 안의 조건식을 "할인이 불가능한경우"를 암시하는 별도의 메서드로 분리하면 더더욱 읽기 좋아질 것 같습니다!!
@@ -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;
+}

리뷰: 저도 이 부분은 궁금해서 댓글 남기고 갑니다!!
@@ -0,0 +1,40 @@
+package christmas.domain.benefit.model;
+
+import christmas.domain.benefit.model.discount.Discount;
+import christmas.domain.benefit.model.gift.Gifts;
+import christmas.domain.menu.model.MenuAndCount;
+import java.util.Collections;
+import java.util.List;
+
+public class Benefits {
+    private final List<Benefit> benefits;
+
+    public Benefits(List<Benefit> benefits) {
+        this.benefits = benefits;
+    }
+
+    public List<MenuAndCount> getGifts() {
+        return benefits.stream()
+                .filter(benefit -> benefit.getClass().equals(Gifts.class))
+                .map(gift -> ((Gifts) gift).getGifts())
+                .flatMap(List::stream)
+                .toList();
+    }
+
+    public List<Benefit> getBenefits() {
+        return Collections.unmodifiableList(benefits);
+    }
+
+    public int getTotalPrice() {
+        return benefits.stream()
+                .mapToInt(Benefit::getBenefitPrice)
+                .sum();
+    }
+
+    public int getDiscountPrice() {
+        return benefits.stream()
+                .filter(benefit -> benefit.getClass().equals(Discount.class))
+                .mapToInt(Benefit::getBenefitPrice)
+                .sum();
+    }
+}

리뷰: getClass 대신 instanceOf를 사용할수도 있을 것 같습니다!
@@ -0,0 +1,40 @@
+package christmas.domain.benefit.model;
+
+import christmas.domain.benefit.model.discount.Discount;
+import christmas.domain.benefit.model.gift.Gifts;
+import christmas.domain.menu.model.MenuAndCount;
+import java.util.Collections;
+import java.util.List;
+
+public class Benefits {
+    private final List<Benefit> benefits;
+
+    public Benefits(List<Benefit> benefits) {
+        this.benefits = benefits;
+    }
+
+    public List<MenuAndCount> getGifts() {
+        return benefits.stream()
+                .filter(benefit -> benefit.getClass().equals(Gifts.class))
+                .map(gift -> ((Gifts) gift).getGifts())
+                .flatMap(List::stream)
+                .toList();
+    }
+
+    public List<Benefit> getBenefits() {
+        return Collections.unmodifiableList(benefits);
+    }
+
+    public int getTotalPrice() {
+        return benefits.stream()
+                .mapToInt(Benefit::getBenefitPrice)
+                .sum();
+    }
+
+    public int getDiscountPrice() {
+        return benefits.stream()
+                .filter(benefit -> benefit.getClass().equals(Discount.class))
+                .mapToInt(Benefit::getBenefitPrice)
+                .sum();
+    }
+}

리뷰: 이 부분에서 캐스팅 말고 더 좋은 방법이 있는지 다른 리뷰어분들의 의견이 궁금합니다! 저도 고민했는데 답을 못 내렸네요
@@ -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;
+    }
+}

리뷰: 저는 june님의 방법도, zangsu님의 방법도 좋다고 생각합니다! Factory를 별도로 정의하니 코드량 자체는 많아지긴 했지만, 각 할인 생성의 방법이 명확하게 들어올 수 있었던 것 같습니다!
@@ -0,0 +1,82 @@
+package christmas.controller;
+
+import java.util.Set;
+import java.util.function.Supplier;
+
+import christmas.domain.event.PresentEvent;
+import christmas.dto.result.PresentEventResult;
+import christmas.service.EventPlannerService;
+import christmas.domain.order.OrderSheet;
+import christmas.dto.order.OrderInput;
+import christmas.dto.order.VisitDate;
+import christmas.dto.result.EventResult;
+import christmas.exception.CommonIllegalArgumentException;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+
+public class EventPlannerController {
+	private final EventPlannerService eventPlannerService;
+
+	public EventPlannerController(EventPlannerService eventPlannerService) {
+		this.eventPlannerService = eventPlannerService;
+	}
+
+	public void run() {
+		OutputView.printGreeting();
+
+		OrderSheet orderSheet = getOrders();
+
+		OutputView.printStartEventPlanner(orderSheet.getVisitDate());
+
+		showOrders(orderSheet);
+		showEventBenefits(orderSheet);
+	}
+
+	private OrderSheet getOrders() {
+		VisitDate visitDate = tryUntilInputIsValid(this::getVisitDate);
+		OrderInput orderInput = tryUntilInputIsValid(this::getOrderInput);
+		return eventPlannerService.createOrderSheet(visitDate, orderInput);
+	}
+
+	private VisitDate getVisitDate() {
+		return new VisitDate(InputView.readVisitDate());
+	}
+
+	private OrderInput getOrderInput() {
+		return new OrderInput(InputView.readOrders());
+	}
+
+	private void showOrders(OrderSheet orderSheet) {
+		OutputView.printOrders(orderSheet.getOrders());
+		OutputView.printTotalPayment(orderSheet.getTotalPayment());
+	}
+
+	private void showEventBenefits(OrderSheet orderSheet) {
+		PresentEventResult presentEventResult = eventPlannerService.getSpecificEventResult(orderSheet, PresentEvent.class);
+		OutputView.printPresentEventResult(presentEventResult);
+
+		Set<EventResult> results = eventPlannerService.getEventResults(orderSheet);
+		OutputView.printEventResults(results);
+
+		int totalBenefits = eventPlannerService.getTotalBenefits(results);
+		OutputView.printEventBenefits(totalBenefits);
+
+		int expectedPayment = eventPlannerService.getExpectedPayment(orderSheet);
+		OutputView.printExpectedPayment(expectedPayment);
+
+		showBadge(totalBenefits);
+	}
+
+	private void showBadge(int totalBenefits) {
+		OutputView.printBadgeResult(eventPlannerService.getBadge(totalBenefits));
+	}
+
+	private <T> T tryUntilInputIsValid(Supplier<T> function) {
+		try {
+			return function.get();
+		} catch (CommonIllegalArgumentException e) {
+			System.out.println(e.getMessage());
+			return tryUntilInputIsValid(function);
+		}
+	}
+}

리뷰: 반복 구현하신거 멋집니다!
@@ -0,0 +1,29 @@
+package christmas.domain.badge;
+
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+public enum Badge {
+	STAR("별", benefits -> (5_000 <= benefits && benefits < 10_000)),
+	TREE("트리", benefits -> (10_000 <= benefits && benefits < 20_000)),
+	SANTA("산타", benefits -> (20_000 <= benefits));
+
+	private String name;
+	private Predicate<Integer> condition;
+
+	Badge(String name, Predicate<Integer> condition) {
+		this.name = name;
+		this.condition = condition;
+	}
+
+	public static Badge of(int benefits) {
+		return Arrays.stream(Badge.values())
+				.filter(badge -> badge.condition.test(benefits))
+				.findFirst()
+				.orElse(null);
+	}
+
+	public String getName() {
+		return name;
+	}
+}

리뷰: - 개인적으로는 NOTHING이라는 enum 객체를 사용하시는 것을 추천드립니다. (이거 메서드를 사용할 때 null을 반환할 수 도 있다는 점을 간과할 수 있어요)
- 혹은 [Optional](https://mangkyu.tistory.com/70)을 사용하시는 것을 추천드립니다.
@@ -0,0 +1,38 @@
+package christmas.domain.event;
+
+import java.time.LocalDate;
+
+import christmas.domain.order.OrderSheet;
+import christmas.dto.result.ChristmasDDayEventResult;
+import christmas.dto.result.EventResult;
+
+public class ChristmasDDayEvent extends Event implements Discountable{
+	private static final int baseDiscount = 1_000;
+	private static final int increment = 100;
+
+	public ChristmasDDayEvent(EventType eventType, LocalDate startDate, LocalDate endDate) {
+		super(eventType, startDate, endDate);
+	}
+
+	@Override
+	public boolean isNotSatisfiedBy(OrderSheet orderSheet) {
+		return isDayOfWeekNotInDuration(orderSheet.getVisitDate());
+	}
+
+	@Override
+	public EventResult getEventBenefits(OrderSheet orderSheet) {
+		if (isNotSatisfiedBy(orderSheet)) {
+			return null;
+		}
+
+		return new ChristmasDDayEventResult(eventType.getName(), calculateDiscount(orderSheet));
+	}
+
+	private int calculateDiscount(OrderSheet orderSheet) {
+		return (baseDiscount + (getDuration(orderSheet) * increment));
+	}
+
+	private int getDuration(OrderSheet orderSheet){
+		return orderSheet.getVisitDate().getDayOfMonth() - startDate.getDayOfMonth();
+	}
+}

리뷰: 위에서도 언급했지만 null을 반환하실 것이라면 Optional을 사용하시는 것을 추천드립니다.
@@ -0,0 +1,41 @@
+package christmas.domain.event;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+
+import christmas.domain.menu.MenuType;
+import christmas.domain.order.OrderSheet;
+import christmas.dto.result.EventResult;
+import christmas.dto.result.WeekendEventResult;
+
+public class WeekdayEvent extends Event implements Discountable{
+	private static final MenuType applicableMenuType = MenuType.DESSERT;
+	private static final int discount = 2_023;
+
+	public WeekdayEvent(EventType eventType, LocalDate startDate, LocalDate endDate) {
+		super(eventType, startDate, endDate);
+	}
+
+	@Override
+	protected boolean isNotSatisfiedBy(OrderSheet orderSheet) {
+		return isDayOfWeekNotInDuration(orderSheet.getVisitDate()) || isNotWeekDay(orderSheet.getVisitDate());
+	}
+
+	@Override
+	public EventResult getEventBenefits(OrderSheet orderSheet) {
+		if (isNotSatisfiedBy(orderSheet)) {
+			return null;
+		}
+
+		int benefitSum = orderSheet.getOrders().entrySet().stream()
+			.filter(order -> order.getKey().getType().equals(applicableMenuType))
+			.mapToInt(order -> order.getValue() * discount)
+			.sum();
+
+		return new WeekendEventResult(eventType.getName(), benefitSum);
+	}
+
+	private boolean isNotWeekDay(LocalDate localDate) {
+		return localDate.getDayOfWeek().equals(DayOfWeek.SATURDAY) || localDate.getDayOfWeek().equals(DayOfWeek.FRIDAY);
+	}
+}

리뷰: order에서 get~().get~() 형식으로 사용하는 것을 기차 충돌이라고 합니다. 기차 충돌은 객체지향 프로그래밍에서 많이 지양합니다. order에서 public method로 `boolean isType(Type type)` 를 제공하는 것이 더 좋다고 생각합니다.

- 참고 자료
  - 프리코스 3주차 공통 피드백 "객체는 객체스럽게 사용한다"
  - https://hyesun03.github.io/2019/04/01/method-chain-vs-train-wrek/
@@ -0,0 +1,41 @@
+package christmas.domain.event;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+
+import christmas.domain.menu.MenuType;
+import christmas.domain.order.OrderSheet;
+import christmas.dto.result.EventResult;
+import christmas.dto.result.WeekendEventResult;
+
+public class WeekdayEvent extends Event implements Discountable{
+	private static final MenuType applicableMenuType = MenuType.DESSERT;
+	private static final int discount = 2_023;
+
+	public WeekdayEvent(EventType eventType, LocalDate startDate, LocalDate endDate) {
+		super(eventType, startDate, endDate);
+	}
+
+	@Override
+	protected boolean isNotSatisfiedBy(OrderSheet orderSheet) {
+		return isDayOfWeekNotInDuration(orderSheet.getVisitDate()) || isNotWeekDay(orderSheet.getVisitDate());
+	}
+
+	@Override
+	public EventResult getEventBenefits(OrderSheet orderSheet) {
+		if (isNotSatisfiedBy(orderSheet)) {
+			return null;
+		}
+
+		int benefitSum = orderSheet.getOrders().entrySet().stream()
+			.filter(order -> order.getKey().getType().equals(applicableMenuType))
+			.mapToInt(order -> order.getValue() * discount)
+			.sum();
+
+		return new WeekendEventResult(eventType.getName(), benefitSum);
+	}
+
+	private boolean isNotWeekDay(LocalDate localDate) {
+		return localDate.getDayOfWeek().equals(DayOfWeek.SATURDAY) || localDate.getDayOfWeek().equals(DayOfWeek.FRIDAY);
+	}
+}

리뷰: 메서드 이름을 `isWeekend()`로 하시면 더 읽기 쉬워집니다.

> notFound, nonDone, notSuccessful 과 같은 이름은 변수의 값이 부정이 됐을 때 읽기 어렵다.
참고 자료 : https://tecoble.techcourse.co.kr/post/2020-04-24-variable_naming/
@@ -0,0 +1,28 @@
+package christmas.dto.order;
+
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.YearMonth;
+
+import christmas.exception.IllegalDateException;
+
+public class VisitDate {
+	private final YearMonth visitYearMonth = YearMonth.of(2023, 12);
+	private final LocalDate visitDate;
+
+	public VisitDate(int input) {
+		this.visitDate = convertBy(input);
+	}
+
+	public LocalDate getVisitDate() {
+		return this.visitDate;
+	}
+
+	private LocalDate convertBy(int input) {
+		try {
+			return visitYearMonth.atDay(input);
+		} catch (DateTimeException e) {
+			throw new IllegalDateException();
+		}
+	}
+}

리뷰: 오! 이런게 있는 줄 몰랐네요! 배워갑니다!
@@ -1,7 +1,11 @@
 package christmas;
 
+import christmas.controller.EventPlannerController;
+
 public class Application {
     public static void main(String[] args) {
-        // TODO: 프로그램 구현
+        AppConfig appConfig = new AppConfig();
+        EventPlannerController controller = new EventPlannerController(appConfig.createEventPlanner());
+        controller.run();
     }
 }

리뷰: 해당 파일에서만 들여쓰기가 4칸이고 다른 파일에서는 8칸이네요.

- 참고 자료 : [Java 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java)
> 4.2 블럭 들여쓰기: +4 스페이스
새 블록 또는 블록과 유사한 구조(block-like construct)가 열릴 때마다 들여쓰기가 네 칸씩 증가합니다. 블록이 끝나면 들여쓰기는 이전 들여쓰기 단계로 돌아갑니다. 들여쓰기 단계는 블록 전체의 코드와 주석 모두에 적용됩니다.
@@ -0,0 +1,29 @@
+package christmas.domain.badge;
+
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+public enum Badge {
+	STAR("별", benefits -> (5_000 <= benefits && benefits < 10_000)),
+	TREE("트리", benefits -> (10_000 <= benefits && benefits < 20_000)),
+	SANTA("산타", benefits -> (20_000 <= benefits));
+
+	private String name;
+	private Predicate<Integer> condition;
+
+	Badge(String name, Predicate<Integer> condition) {
+		this.name = name;
+		this.condition = condition;
+	}
+
+	public static Badge of(int benefits) {
+		return Arrays.stream(Badge.values())
+				.filter(badge -> badge.condition.test(benefits))
+				.findFirst()
+				.orElse(null);
+	}
+
+	public String getName() {
+		return name;
+	}
+}

리뷰: null 처리가 고민이었는데 NOTHING을 반환하는 것도 좋은 방법같아요 👍
@@ -0,0 +1,41 @@
+package christmas.domain.event;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+
+import christmas.domain.menu.MenuType;
+import christmas.domain.order.OrderSheet;
+import christmas.dto.result.EventResult;
+import christmas.dto.result.WeekendEventResult;
+
+public class WeekdayEvent extends Event implements Discountable{
+	private static final MenuType applicableMenuType = MenuType.DESSERT;
+	private static final int discount = 2_023;
+
+	public WeekdayEvent(EventType eventType, LocalDate startDate, LocalDate endDate) {
+		super(eventType, startDate, endDate);
+	}
+
+	@Override
+	protected boolean isNotSatisfiedBy(OrderSheet orderSheet) {
+		return isDayOfWeekNotInDuration(orderSheet.getVisitDate()) || isNotWeekDay(orderSheet.getVisitDate());
+	}
+
+	@Override
+	public EventResult getEventBenefits(OrderSheet orderSheet) {
+		if (isNotSatisfiedBy(orderSheet)) {
+			return null;
+		}
+
+		int benefitSum = orderSheet.getOrders().entrySet().stream()
+			.filter(order -> order.getKey().getType().equals(applicableMenuType))
+			.mapToInt(order -> order.getValue() * discount)
+			.sum();
+
+		return new WeekendEventResult(eventType.getName(), benefitSum);
+	}
+
+	private boolean isNotWeekDay(LocalDate localDate) {
+		return localDate.getDayOfWeek().equals(DayOfWeek.SATURDAY) || localDate.getDayOfWeek().equals(DayOfWeek.FRIDAY);
+	}
+}

리뷰: 오,, 이부분도 구현하면서 만족스럽지 않아서 고민이 많았는데 조언해주신 방법대로 Order 내부에서 확인하는게 더 좋았을 것 같네요.
@@ -1,7 +1,11 @@
 package christmas;
 
+import christmas.controller.EventPlannerController;
+
 public class Application {
     public static void main(String[] args) {
-        // TODO: 프로그램 구현
+        AppConfig appConfig = new AppConfig();
+        EventPlannerController controller = new EventPlannerController(appConfig.createEventPlanner());
+        controller.run();
     }
 }

리뷰: 분명 4칸으로 설정했는데 왜 이렇게 된지 모르겠네요 😭
@@ -0,0 +1,29 @@
+package christmas.domain.badge;
+
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+public enum Badge {
+	STAR("별", benefits -> (5_000 <= benefits && benefits < 10_000)),
+	TREE("트리", benefits -> (10_000 <= benefits && benefits < 20_000)),
+	SANTA("산타", benefits -> (20_000 <= benefits));
+
+	private String name;
+	private Predicate<Integer> condition;
+
+	Badge(String name, Predicate<Integer> condition) {
+		this.name = name;
+		this.condition = condition;
+	}
+
+	public static Badge of(int benefits) {
+		return Arrays.stream(Badge.values())
+				.filter(badge -> badge.condition.test(benefits))
+				.findFirst()
+				.orElse(null);
+	}
+
+	public String getName() {
+		return name;
+	}
+}

리뷰: Predicate로 구현하니 훨씬 깔끔하네요!
@@ -0,0 +1,26 @@
+package christmas.domain.event;
+
+import java.time.LocalDate;
+
+import christmas.domain.order.OrderSheet;
+import christmas.dto.result.EventResult;
+
+public abstract class Event {
+	EventType eventType;
+	LocalDate startDate;
+	LocalDate endDate;
+
+	public Event(EventType eventType, LocalDate startDate, LocalDate endDate) {
+		this.eventType = eventType;
+		this.startDate = startDate;
+		this.endDate = endDate;
+	}
+
+	protected boolean isDayOfWeekNotInDuration(LocalDate localDate) {
+		return localDate.isBefore(startDate) || localDate.isAfter(endDate);
+	}
+
+	protected abstract boolean isNotSatisfiedBy(OrderSheet orderSheet);
+
+	public abstract EventResult getEventBenefits(OrderSheet orderSheet);
+}

리뷰: 추상 클래스와 인터페이스 활용이 멋지네요! 배워갑니다
@@ -0,0 +1,9 @@
+package christmas.exception;
+
+public class CommonIllegalArgumentException extends IllegalArgumentException {
+	public static final String EXCEPTION_PREFIX = "[ERROR] %s";
+
+	public CommonIllegalArgumentException(String message) {
+		super(String.format(EXCEPTION_PREFIX, message));
+	}
+}

리뷰: EXCEPTION_PREFIX, INVALID_ORDER_MESSAGE 등의 예외 메시지는 여러 클래스에서 사용되는데 Enum으로 구현하지 않으신 이유가 궁금합니다.
@@ -0,0 +1,9 @@
+package christmas.exception;
+
+public class CommonIllegalArgumentException extends IllegalArgumentException {
+	public static final String EXCEPTION_PREFIX = "[ERROR] %s";
+
+	public CommonIllegalArgumentException(String message) {
+		super(String.format(EXCEPTION_PREFIX, message));
+	}
+}

리뷰: 상수들을 무조건 ENUM 으로 관리하기보다는 필요한 클래스 내부에 위치시키는게 맞다고 생각해서 ENUM으로 구현하지 않았습니다.
저도 이 부분을 다시 보면서 고민이 많았는데, 공통된 출력 형식이 있다면 ENUM으로 관리했어도 괜찮았을 것 같네요 !