ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -18,39 +18,30 @@ public OrderService(OrderRepository orderRepository) {
         this.orderRepository = orderRepository;
     }
 
-    public void createOrder(Order order){
+    public void createOrder(Order order) {
         orderRepository.createOrder(order);
     }
 
-    public List<Order> getOrderList(String storeId){
+    public List<Order> getOrderList(String storeId) {
         return orderRepository.readOrderList(storeId);
     }
 
-    public Optional<Order> getOrder(String orderId){
+    public Optional<Order> getOrder(String orderId) {
         return orderRepository.readOrder(orderId);
     }
 
-    public OrderStatus changeOrderStatusToOrderAccept(String orderId, OrderStatus orderStatus){
-        if(orderStatus.equals(OrderStatus.ORDER_REQUEST)){
-            return orderRepository.updateOrderStatus(orderId, OrderStatus.ORDER_ACCEPT);
-        } else {
-            throw new IllegalStateException("The current order status is not changeable");
-        }
-    }
+    public OrderStatus changeOrderStatus(Order order, OrderStatus status, OrderStatusUpdatePolicy orderStatusUpdatePolicy) {
+        orderStatusUpdatePolicy.checkStatusUpdatable(status);
 
-    public OrderStatus changeOrderStatusToFoodReady(String orderId, OrderStatus orderStatus){
-        if (orderStatus.equals(OrderStatus.RIDER_ASSIGNED)) {
-            return orderRepository.updateOrderStatus(orderId, OrderStatus.FOOD_READY);
-        } else {
-            throw new IllegalStateException("The current order status is not changeable");
-        }
+        return orderRepository.updateOrderStatus(order.getOrderId(), status);
     }
 
-    public void assignRiderToOrder(String orderId, OrderStatus orderStatus, RiderPartDto riderPartDto){
+    public void assignRiderToOrder(String orderId, OrderStatus orderStatus, RiderPartDto riderPartDto) {
         orderRepository.updateRiderInfo(orderId, orderStatus, riderPartDto);
     }
 
-    public void changeOrderStatusFromOtherServer(String orderId, OrderStatus orderStatus){
+    public void changeOrderStatusFromOtherServer(String orderId, OrderStatus orderStatus) {
         orderRepository.updateOrderStatus(orderId, orderStatus);
     }
+
 }

리뷰: (C) changeOrderStatusTo~ 메서드의 내용이 대부분 중복되어서, 해당 메서드들을 changeOrderStatus라는 메서드로 통합하였습니다.

여기서 새로운 클래스인 OrderStatusUpdatePolicy가 만들어졌는데, ~Policy라는 명칭은 서비스 정책을 표현하기 위해 범용적으로 사용하는 클래스 네이밍 중 하나입니다.

delivery-application에도 서비스 운영에 필요한 여러가지 정책들을 갖고 있을텐데요, 이런 정책들은 클래스로 드러내지 않고 메서드나 로직으로만 나타내면 정책들이 분산되고, 암시적으로 표현되어서 우리 서비스가 어떤 정책들을 갖고 있는지 명확히 파악하기 어렵습니다. (이렇게 되면 시간이 지나면서 코드가 서비스 정책을 따라가지 못하는 상황도 종종 생깁니다)

DDD를 아직 읽어보신지는 모르겠지만, [암시적인 개념을 명확하게](https://dhsim86.github.io/programming/2019/05/16/domain_driven_design_09-post.html)라는 chapter가 있는데 시간나실 때 링크 한번 읽어보시고, 현재 우리 서비스에 여기저기 숨어있는 정책들은 어떤 것들이 있을지 찾아보셔서 명확하게 클래스로 나타내보신다면 많은 도움이 되실 것 같습니다 :)
@@ -0,0 +1,13 @@
+package msa.customer.exception;
+
+public class DeliveryCustomerException extends RuntimeException {
+
+    public DeliveryCustomerException(String message) {
+        super(message);
+    }
+
+    public DeliveryCustomerException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}

리뷰: 참고하겠습니다.
@@ -0,0 +1,20 @@
+package christmas.exception;
+
+public enum ExceptionMessage {
+    INVALID_INPUT_DATE("유효하지 않은 날짜입니다. 다시 입력해 주세요."),
+    INVALID_INPUT_ORDER("유효하지 않은 주문입니다. 다시 입력해 주세요."),
+    INVALID_MENU_ITEM("해당 메뉴가 존재하지 않습니다."),
+    INVALID_WEEK_DISCOUNT_TYPE("잘못된 주간 할인 타입입니다."),
+    ;
+
+    private static final String PREFIX = "[ERROR] ";
+    private final String errorMessage;
+
+    ExceptionMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    public String getErrorMessage() {
+        return PREFIX + errorMessage;
+    }
+}

리뷰: 추가될 enum을 고려하셔서 이렇게 작성한 의도라고 생각은 되지만, 꼼꼼하고 파고들자면 ,;보다 ;로 끝내는게 더 좋지 않을까요?? 개인적인 생각입니다 ㅎㅎㅎ
@@ -1,7 +1,20 @@
 package christmas;
 
+
+import camp.nextstep.edu.missionutils.Console;
+import christmas.run.PromotionRun;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+
 public class Application {
     public static void main(String[] args) {
-        // TODO: 프로그램 구현
+        try {
+            InputView inputView = SingletonView.getInputView();
+            OutputView outputView = SingletonView.getOutputView();
+            PromotionRun promotionRun = new PromotionRun(inputView, outputView);
+            promotionRun.runPromotion();
+        } finally {
+            Console.close();
+        }
     }
 }

리뷰: 뷰를 싱글톤으로 관리하신 이유가 궁금합니다!
@@ -0,0 +1,20 @@
+package christmas.exception;
+
+public enum ExceptionMessage {
+    INVALID_INPUT_DATE("유효하지 않은 날짜입니다. 다시 입력해 주세요."),
+    INVALID_INPUT_ORDER("유효하지 않은 주문입니다. 다시 입력해 주세요."),
+    INVALID_MENU_ITEM("해당 메뉴가 존재하지 않습니다."),
+    INVALID_WEEK_DISCOUNT_TYPE("잘못된 주간 할인 타입입니다."),
+    ;
+
+    private static final String PREFIX = "[ERROR] ";
+    private final String errorMessage;
+
+    ExceptionMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    public String getErrorMessage() {
+        return PREFIX + errorMessage;
+    }
+}

리뷰: 추가적으로 요구사항에 에러메시지는 "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요."으로 통일하라고 기재되어 있어 최종코딩테스트로 가시게 되면 꼼꼼히 보시면 좋을 것 같습니다.
@@ -0,0 +1,33 @@
+package christmas.model.event;
+
+import christmas.model.date.ReservationDate;
+
+public class ChristmasDiscount {
+    private static final int FIRST_DAY = 1;
+    private static final int CHRISTMAS_DAY = 25;
+    private static final int BASIC_DISCOUNT = 1000;
+    private static final int PLUS_DISCOUNT = 100;
+    private static final int NONE_DISCOUNT = 0;
+
+    private final int discount;
+
+    public ChristmasDiscount(ReservationDate date) {
+        this.discount = initDiscountAmount(date);
+    }
+
+    private int initDiscountAmount(ReservationDate date) {
+        if (date.day() > CHRISTMAS_DAY) {
+            return NONE_DISCOUNT;
+        }
+
+        return BASIC_DISCOUNT + getPlusDiscount(date.day());
+    }
+
+    private int getPlusDiscount(int day) {
+        return (day - FIRST_DAY) * PLUS_DISCOUNT;
+    }
+
+    public int getDiscount() {
+        return discount;
+    }
+}

리뷰: ReservationDate에서 스스로 자신이 가진 값에대해 검증하도록 메소드를 구현하시는 것이 더 객체지향적인 코드라고 생각합니다.
date.isChristmasDay() 와 같이 말이죠!! 'getter를 지양하라'는 의미는 VO에게도 동일하게 적용됩니다.

관련 문서를 아래 링크로 달아둘게요!
https://tecoble.techcourse.co.kr/post/2020-04-28-ask-instead-of-getter/
@@ -0,0 +1,62 @@
+package christmas.model.event;
+
+import christmas.model.event.dto.ReservationDateEventDTO;
+
+public class DiscountDetail {
+    private final int totalPriceBeforeDiscount;
+    private final int totalPriceAfterDiscount;
+    private final WeekDiscountType weekDiscountType;
+    private final int christmasDiscount;
+    private final int weekTypeDiscount;
+    private final int specialDiscount;
+
+    protected DiscountDetail(ReservationDateEventDTO dateEventDTO, int totalPriceBeforeDiscount, int weekTypeDiscount) {
+        this.totalPriceBeforeDiscount = totalPriceBeforeDiscount;
+        this.weekDiscountType = WeekDiscountType.checkWeekType(dateEventDTO.getDateWeek());
+        this.christmasDiscount = dateEventDTO.getChristmasDiscount();
+        this.specialDiscount = dateEventDTO.getSpecialDiscount();
+        this.weekTypeDiscount = weekTypeDiscount;
+        this.totalPriceAfterDiscount = initTotalPriceAfterDiscount();
+    }
+
+    protected DiscountDetail(ReservationDateEventDTO dateEventDTO, int totalPriceBeforeDiscount) {
+        this.totalPriceBeforeDiscount = totalPriceBeforeDiscount;
+        this.weekDiscountType = WeekDiscountType.checkWeekType(dateEventDTO.getDateWeek());
+        this.christmasDiscount = 0;
+        this.specialDiscount = 0;
+        this.weekTypeDiscount = 0;
+        this.totalPriceAfterDiscount = initTotalPriceAfterDiscount();
+    }
+
+    private int initTotalPriceAfterDiscount() {
+        return totalPriceBeforeDiscount - getTotalDiscount();
+    }
+
+    public int getTotalPriceBeforeDiscount() {
+        return totalPriceBeforeDiscount;
+    }
+
+    public int getTotalPriceAfterDiscount() {
+        return totalPriceAfterDiscount;
+    }
+
+    public int getChristmasDiscount() {
+        return christmasDiscount;
+    }
+
+    public int getSpecialDiscount() {
+        return specialDiscount;
+    }
+
+    public int getWeekTypeDiscount() {
+        return weekTypeDiscount;
+    }
+
+    public WeekDiscountType getWeekDiscountType() {
+        return weekDiscountType;
+    }
+
+    public int getTotalDiscount() {
+        return christmasDiscount + weekTypeDiscount + specialDiscount;
+    }
+}

리뷰: DiscountDetil의 필드로 이 값들을 굳이 가져야 할 이유가 있을까요? 재사용의 여지가 없다면 메소드를 통해 메시지를 전달하는 방식이 더 좋다고 생각해요!
@@ -0,0 +1,62 @@
+package christmas.model.event;
+
+import christmas.model.event.dto.ReservationDateEventDTO;
+
+public class DiscountDetail {
+    private final int totalPriceBeforeDiscount;
+    private final int totalPriceAfterDiscount;
+    private final WeekDiscountType weekDiscountType;
+    private final int christmasDiscount;
+    private final int weekTypeDiscount;
+    private final int specialDiscount;
+
+    protected DiscountDetail(ReservationDateEventDTO dateEventDTO, int totalPriceBeforeDiscount, int weekTypeDiscount) {
+        this.totalPriceBeforeDiscount = totalPriceBeforeDiscount;
+        this.weekDiscountType = WeekDiscountType.checkWeekType(dateEventDTO.getDateWeek());
+        this.christmasDiscount = dateEventDTO.getChristmasDiscount();
+        this.specialDiscount = dateEventDTO.getSpecialDiscount();
+        this.weekTypeDiscount = weekTypeDiscount;
+        this.totalPriceAfterDiscount = initTotalPriceAfterDiscount();
+    }
+
+    protected DiscountDetail(ReservationDateEventDTO dateEventDTO, int totalPriceBeforeDiscount) {
+        this.totalPriceBeforeDiscount = totalPriceBeforeDiscount;
+        this.weekDiscountType = WeekDiscountType.checkWeekType(dateEventDTO.getDateWeek());
+        this.christmasDiscount = 0;
+        this.specialDiscount = 0;
+        this.weekTypeDiscount = 0;
+        this.totalPriceAfterDiscount = initTotalPriceAfterDiscount();
+    }
+
+    private int initTotalPriceAfterDiscount() {
+        return totalPriceBeforeDiscount - getTotalDiscount();
+    }
+
+    public int getTotalPriceBeforeDiscount() {
+        return totalPriceBeforeDiscount;
+    }
+
+    public int getTotalPriceAfterDiscount() {
+        return totalPriceAfterDiscount;
+    }
+
+    public int getChristmasDiscount() {
+        return christmasDiscount;
+    }
+
+    public int getSpecialDiscount() {
+        return specialDiscount;
+    }
+
+    public int getWeekTypeDiscount() {
+        return weekTypeDiscount;
+    }
+
+    public WeekDiscountType getWeekDiscountType() {
+        return weekDiscountType;
+    }
+
+    public int getTotalDiscount() {
+        return christmasDiscount + weekTypeDiscount + specialDiscount;
+    }
+}

리뷰: 외부 생성을 막기 위해 생성자를 protected로 관리하신 것으로 보이는데, private으로 생성자를 막고 정적팩토리메소드 생성자를 사용하면 더 엄밀하게 캡슐화할 수 있습니다! 관련 자료를 아래 링크로 달아둘게요!

https://hudi.blog/effective-java-static-factory-method/
@@ -0,0 +1,28 @@
+package christmas.model.event;
+
+public enum SpecialDayDiscount {
+    THIRD_DAY(3, 1000),
+    TENTH_DAY(10, 1000),
+    SEVENTEENTH_DAY(17, 1000),
+    TWENTY_FOURTH_DAY(24, 1000),
+    TWENTY_FIFTH_DAY(25, 1000),
+    THIRTY_FIRST_DAY(31, 1000),
+    OTHER_DAY(0, 0),
+    ;
+
+    private final int day;
+    private final int discountPrice;
+
+    SpecialDayDiscount(int day, int discountPrice) {
+        this.day = day;
+        this.discountPrice = discountPrice;
+    }
+
+    public int getDay() {
+        return day;
+    }
+
+    public int getDiscountPrice() {
+        return discountPrice;
+    }
+}

리뷰: 1000원이라는 동일한 할인금액을 가지고 있으니 List로 관리하는 방법도 괜찮을 것 같습니다!
@@ -0,0 +1,55 @@
+package christmas.model.menu;
+
+import christmas.exception.ExceptionMessage;
+import christmas.exception.PromotionException;
+
+import java.util.Arrays;
+
+public enum MenuItem {
+    APPETIZER_MUSHROOM_SOUP("양송이수프", 6_000, MenuCategory.APPETIZER),
+    APPETIZER_TAPAS("타파스", 5_500, MenuCategory.APPETIZER),
+    APPETIZER_CAESAR_SALAD("시저샐러드", 8_000, MenuCategory.APPETIZER),
+
+    MAIN_T_BONE_STEAK("티본스테이크", 55_000, MenuCategory.MAIN_COURSE),
+    MAIN_BBQ_RIB("바비큐립", 54_000, MenuCategory.MAIN_COURSE),
+    MAIN_SEAFOOD_PASTA("해산물파스타", 35_000, MenuCategory.MAIN_COURSE),
+    MAIN_CHRISTMAS_PASTA("크리스마스파스타", 25_000, MenuCategory.MAIN_COURSE),
+
+    DESSERT_CHOCO_CAKE("초코케이크", 15_000, MenuCategory.DESSERT),
+    DESSERT_ICE_CREAM("아이스크림", 5_000, MenuCategory.DESSERT),
+
+    BEVERAGE_ZERO_COLA("제로콜라", 3_000, MenuCategory.BEVERAGE),
+    BEVERAGE_RED_WINE("레드와인", 60_000, MenuCategory.BEVERAGE),
+    BEVERAGE_CHAMPAGNE("샴페인", 25_000, MenuCategory.BEVERAGE),
+    ;
+
+    private final String itemName;
+    private final int itemPrice;
+    private final MenuCategory menuCategory;
+
+    MenuItem(String itemName, int itemPrice, MenuCategory menuCategory) {
+        this.itemName = itemName;
+        this.itemPrice = itemPrice;
+        this.menuCategory = menuCategory;
+    }
+
+    public static MenuItem findMenuItemByName(String itemName, ExceptionMessage message) {
+        return Arrays.stream(MenuItem.values())
+                .filter(menu -> menu.getItemName().equals(itemName))
+                .findFirst()
+                .orElseThrow(() ->
+                        new PromotionException(message));
+    }
+
+    public String getItemName() {
+        return itemName;
+    }
+
+    public int getItemPrice() {
+        return itemPrice;
+    }
+
+    public MenuCategory getMenuCategory() {
+        return menuCategory;
+    }
+}

리뷰: MenuCategory, MenuItem에서 중복되는 요소들이 있는데 하나로 묶어도  될 것 같아요!
@@ -0,0 +1,79 @@
+package christmas.model.menu;
+
+import christmas.exception.ExceptionMessage;
+import christmas.exception.PromotionException;
+
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class OrderMenus {
+    private static final int MIN_ORDER_MENU_NUMBER = 1;
+    private static final int MAX_ORDER_MENUS_NUMBER = 20;
+
+    private final Map<MenuItem, Integer> orderMenus;
+
+    public OrderMenus(Map<String, Integer> orderMenus) {
+        this.orderMenus = validateOrderMenusByNames(orderMenus);
+        validateOrderMenus();
+    }
+
+    private Map<MenuItem, Integer> validateOrderMenusByNames(Map<String, Integer> orderMenus) {
+        return generateOrderMenuValues(orderMenus)
+                .collect(Collectors.toUnmodifiableMap(
+                        entry -> MenuItem.findMenuItemByName(entry.getKey(), ExceptionMessage.INVALID_INPUT_ORDER),
+                        Map.Entry::getValue
+                ));
+    }
+
+    private void validateOrderMenus() {
+        validateOrderNumbers();
+        validateTotalOrderNumbers();
+        validateOrderAllBeverage();
+    }
+
+    private void validateOrderNumbers() {
+        generateOrderMenuValues(orderMenus)
+                .filter(menu ->
+                        menu.getValue() < MIN_ORDER_MENU_NUMBER)
+                .findAny()
+                .ifPresent(menuName -> throwInvalidOrderException());
+    }
+
+    private void validateTotalOrderNumbers() {
+        int orderNumber = generateOrderMenuValues(orderMenus)
+                .mapToInt(Map.Entry::getValue)
+                .sum();
+
+        if (orderNumber > MAX_ORDER_MENUS_NUMBER) {
+            throwInvalidOrderException();
+        }
+    }
+
+    private void validateOrderAllBeverage() {
+        boolean allBeverage = generateOrderMenuValues(orderMenus)
+                .allMatch(orderMenu ->
+                        orderMenu.getKey().getMenuCategory() == MenuCategory.BEVERAGE);
+
+        if (allBeverage) {
+            throwInvalidOrderException();
+        }
+    }
+
+    private <K, V> Stream<Map.Entry<K, V>> generateOrderMenuValues(Map<K, V> orderMenus) {
+        return orderMenus.entrySet()
+                .stream();
+    }
+
+    private void throwInvalidOrderException() {
+        throw new PromotionException(ExceptionMessage.INVALID_INPUT_ORDER);
+    }
+
+    public Map<String, Integer> getOrderMenus() {
+        Map<String, Integer> menuNames = new HashMap<>();
+        orderMenus.forEach((menuItem, quantity) ->
+                menuNames.put(menuItem.getItemName(), quantity));
+
+        return Collections.unmodifiableMap(menuNames);
+    }
+}

리뷰: Integer도 원시값을 포장하는 것은 어떨까요??
@@ -0,0 +1,76 @@
+package christmas.run;
+
+import christmas.controller.PromotionController;
+import christmas.exception.PromotionException;
+import christmas.model.event.dto.EventResultDTO;
+import christmas.model.event.dto.ReservationDateEventDTO;
+import christmas.model.menu.dto.OrderMenusDTO;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+
+import java.util.function.Supplier;
+
+public class PromotionRun {
+    private final PromotionController controller = new PromotionController();
+    private final InputView inputView;
+    private final OutputView outputView;
+
+    public PromotionRun(InputView inputView, OutputView outputView) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+    }
+
+    public void runPromotion() {
+        ReservationDateEventDTO dateEventDto = inputCorrectReservationDate();
+        OrderMenusDTO orderMenusDto = inputCorrectOrderMenus();
+        displayEventPreview(dateEventDto);
+
+        EventResultDTO responseDto = applyEventsAndGenerateResult(dateEventDto, orderMenusDto);
+        outputEventResult(responseDto);
+    }
+
+    private ReservationDateEventDTO inputCorrectReservationDate() {
+        outputView.displayStartPromotion();
+
+        return getCorrectResult(this::generateDateEvent);
+    }
+
+    private OrderMenusDTO inputCorrectOrderMenus() {
+        return getCorrectResult(this::receiveMenus);
+    }
+
+    private ReservationDateEventDTO generateDateEvent() {
+        String inputDate = inputView.inputDate();
+
+        return controller.generateEvent(inputDate);
+    }
+
+    private OrderMenusDTO receiveMenus() {
+        String inputMenus = inputView.inputOrderMenus();
+
+        return controller.receiveOrderMenus(inputMenus);
+    }
+
+    private void displayEventPreview(ReservationDateEventDTO dateEventDto) {
+        outputView.displayPreviewEvent(dateEventDto.getDay());
+    }
+
+    private EventResultDTO applyEventsAndGenerateResult(ReservationDateEventDTO dateEventDTO,
+                                                        OrderMenusDTO orderMenusDto) {
+        return controller.applyEvents(dateEventDTO, orderMenusDto);
+    }
+
+    private void outputEventResult(EventResultDTO responseDto) {
+        outputView.outputEventResult(responseDto);
+    }
+
+    private <T> T getCorrectResult(Supplier<T> supplier) {
+        while (true) {
+            try {
+                return supplier.get();
+            } catch (PromotionException e) {
+                outputView.displayException(e);
+            }
+        }
+    }
+}

리뷰: 필드주입보다 생성자 주입이 유지보수 관점에서 더 좋을 것 같아요.!!
@@ -0,0 +1,76 @@
+package christmas.run;
+
+import christmas.controller.PromotionController;
+import christmas.exception.PromotionException;
+import christmas.model.event.dto.EventResultDTO;
+import christmas.model.event.dto.ReservationDateEventDTO;
+import christmas.model.menu.dto.OrderMenusDTO;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+
+import java.util.function.Supplier;
+
+public class PromotionRun {
+    private final PromotionController controller = new PromotionController();
+    private final InputView inputView;
+    private final OutputView outputView;
+
+    public PromotionRun(InputView inputView, OutputView outputView) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+    }
+
+    public void runPromotion() {
+        ReservationDateEventDTO dateEventDto = inputCorrectReservationDate();
+        OrderMenusDTO orderMenusDto = inputCorrectOrderMenus();
+        displayEventPreview(dateEventDto);
+
+        EventResultDTO responseDto = applyEventsAndGenerateResult(dateEventDto, orderMenusDto);
+        outputEventResult(responseDto);
+    }
+
+    private ReservationDateEventDTO inputCorrectReservationDate() {
+        outputView.displayStartPromotion();
+
+        return getCorrectResult(this::generateDateEvent);
+    }
+
+    private OrderMenusDTO inputCorrectOrderMenus() {
+        return getCorrectResult(this::receiveMenus);
+    }
+
+    private ReservationDateEventDTO generateDateEvent() {
+        String inputDate = inputView.inputDate();
+
+        return controller.generateEvent(inputDate);
+    }
+
+    private OrderMenusDTO receiveMenus() {
+        String inputMenus = inputView.inputOrderMenus();
+
+        return controller.receiveOrderMenus(inputMenus);
+    }
+
+    private void displayEventPreview(ReservationDateEventDTO dateEventDto) {
+        outputView.displayPreviewEvent(dateEventDto.getDay());
+    }
+
+    private EventResultDTO applyEventsAndGenerateResult(ReservationDateEventDTO dateEventDTO,
+                                                        OrderMenusDTO orderMenusDto) {
+        return controller.applyEvents(dateEventDTO, orderMenusDto);
+    }
+
+    private void outputEventResult(EventResultDTO responseDto) {
+        outputView.outputEventResult(responseDto);
+    }
+
+    private <T> T getCorrectResult(Supplier<T> supplier) {
+        while (true) {
+            try {
+                return supplier.get();
+            } catch (PromotionException e) {
+                outputView.displayException(e);
+            }
+        }
+    }
+}

리뷰: 제너릭을 사용하신 부분이 인상깊습니다.!
@@ -0,0 +1,27 @@
+package christmas.util;
+
+enum EventResultText {
+    ORDER_OUTPUT_REGEX("%s %d%s"),
+    EMPTY_TEXT(""),
+    SPACE(" "),
+    MENU_NUMBER("개"),
+    MENU_PRICE_UNIT("원"),
+    CHRISTMAS_D_DAY_DISCOUNT("크리스마스 디데이 할인"),
+    SPECIAL_DISCOUNT("특별 할인"),
+    GIFT_EVENT("증정 이벤트"),
+    NONE_BENEFIT("없음"),
+    DISCOUNT_PRICE("-"),
+    BENEFIT_SEPARATOR(": "),
+    NEW_LINE("\n"),
+    ;
+
+    private final String text;
+
+    EventResultText(String text) {
+        this.text = text;
+    }
+
+    public String getText() {
+        return text;
+    }
+}

리뷰: 연관성이 없는 상수들은 static final로 관리하는 것이 더 가독성 측면에서 좋다고 생각해요 !