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

리뷰: "어쩔 수 없는 부분이다"라고 생각합니다.

아래는 그냥 지금 즉각적으로 든 제 생각입니다. (분명히 책이나 글에서 봤을텐데...)
- 객체는 생성자에서 인자를 통해 주입 받는 것이 테스트 코드를 짜는데 용이하다 (내부에서 생성하지 말고)
- 객체는 서로에 대해 모르면 모를수록 (의존성이 낮을수록) 좋다.

사실 1번을 지킬려면 지금처럼 외부에서 생성해서 넣어주어야 합니다. 그리고 Controller와 도메인 간의 종속성을 줄이려면 (2번을 지키려면) EventDate와 Order를 Plan내부에서 생성해야 하죠. 그냥 지킬 게 많다보면 모두 지킬 수는 없죠.
요구사항 자체가 EventDate의 형식이 다를 때, 바로 다시 입력해야 하니까 1번 규칙을 따랐다고 볼 수 있습니다. 리뷰어님이 생각하신 것에 제대로 답변을 못드린 거 같은데 아래서 이어서 드리겠습니다.