ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,49 @@
+package christmas.controller;
+
+import christmas.config.Constant;
+import christmas.config.EventBadge;
+import christmas.config.Gift;
+import christmas.model.Order;
+import christmas.model.Promotion;
+import christmas.model.PromotionGenerator;
+import christmas.model.VisitDate;
+import christmas.view.OutputView;
+
+public class PromotionController {
+    private final VisitDate visitDate;
+    private final Order order;
+    private final Promotion promotion;
+
+    public PromotionController() {
+        visitDate = InputController.setVisitDate();
+        order = InputController.setOrder();
+        promotion = new PromotionGenerator(visitDate, order).getPromotion();
+    }
+
+    public void active() {
+        print();
+    }
+
+    private int totalPrice() {
+        return order.getTotalPrice();
+    }
+
+    private int totalDiscount() {
+        return promotion.getTotalDiscount();
+    }
+
+    private boolean isEmptyPromotion() {
+        return promotion.getTotalDiscount() == 0;
+    }
+
+    private void print() {
+        OutputView.printVisitDate(visitDate.getVisitDate());
+        OutputView.printOrder(order.getOrderList());
+        OutputView.printTotalPrice(totalPrice());
+        OutputView.printGift(Gift.getGift(totalPrice()));
+        OutputView.printPromotion(isEmptyPromotion(), promotion.getPromotionList());
+        OutputView.printTotalDiscount(totalDiscount() * Constant.MINUS);
+        OutputView.printPaymentPrice(totalPrice() - promotion.getDiscount());
+        OutputView.printBadge(EventBadge.findBadgeByTotalDiscount(totalDiscount()));
+    }
+}

리뷰: 메서드 명을 명사로 하는 것은 별로 좋지 않은 것 같아요
@@ -0,0 +1,45 @@
+package christmas.model;
+
+import christmas.config.Event;
+import christmas.config.Gift;
+import christmas.config.Menu;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+public class GiftEvent implements DiscountEventInterface {
+    private final int totalPrice;
+
+    public GiftEvent(int totalPrice) {
+        this.totalPrice = totalPrice;
+    }
+
+    private Gift getGift() {
+        return Gift.getGift(totalPrice);
+    }
+
+    private String getGiftMenu() {
+        return getGift().getMenu();
+    }
+
+    @Override
+    public boolean isDiscountTarget() {
+        return !getGift().equals(Gift.NONE);
+    }
+
+    private int getGiftPrice() {
+        return Menu.getMenu(getGiftMenu())
+                .getPrice();
+    }
+
+    @Override
+    public Map<Event, Integer> getDiscountInfo() {
+        Map<Event, Integer> discountInfo = new EnumMap<>(Event.class);
+
+        if (isDiscountTarget()) {
+            discountInfo.put(Event.GIFT, getGiftPrice());
+        }
+
+        return discountInfo;
+    }
+}

리뷰: 이벤트가 제공하는 공통기능을 인터페이스로 잘 뽑은 것 같아요:+1:
@@ -0,0 +1,46 @@
+package christmas.model;
+
+import christmas.config.Menu;
+import christmas.config.MenuType;
+import christmas.util.Util;
+import christmas.util.validator.OrderGenerateAfterValidator;
+import christmas.util.validator.OrderGenerateBeforeValidator;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class Order {
+    private final Map<Menu, Integer> order;
+
+    public Order(String order) {
+        new OrderGenerateBeforeValidator(order);
+        this.order = new OrderGenerator(order).generateOrder();
+        new OrderGenerateAfterValidator(this);
+    }
+
+    public int getTotalQuantity() {
+        return Util.intStreamSum(order.values().stream());
+    }
+
+    public int getMenuQuantity(MenuType menuType) {
+        return Util.intStreamSum(order.keySet()
+                .stream()
+                .filter(menu -> menu.getType() == menuType)
+                .map(order::get));
+    }
+
+    public int getTotalPrice() {
+        return Util.intStreamSum(order.entrySet()
+                .stream()
+                .map(entry -> entry.getKey().getPrice() * entry.getValue()));
+    }
+
+    public Map<String, Integer> getOrderList() {
+        return order.entrySet()
+                .stream()
+                .collect(Collectors.toMap(
+                        entry -> entry.getKey().getName(),
+                        Map.Entry::getValue
+                ));
+    }
+}

리뷰: 저는 String 값을 입력받아서 Order 객체에서 파싱을 진행했는데 이 부분을 따로 OrderGenerator라는 객체로 분리하니까 더 좋은 것 같아요:+1:
@@ -0,0 +1,42 @@
+package christmas.model;
+
+import christmas.config.Event;
+import christmas.util.Util;
+
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class Promotion {
+    private final Map<Event, Integer> promotion;
+
+    public Promotion() {
+        promotion = new EnumMap<>(Event.class);
+    }
+
+    public Map<String, Integer> getPromotionList() {
+        return promotion.entrySet()
+                .stream()
+                .filter(event -> event.getValue() > 0)
+                .collect(Collectors.toMap(
+                        event -> event.getKey().getName(),
+                        Map.Entry::getValue)
+                );
+    }
+
+    public int getTotalDiscount() {
+        return Util.intStreamSum(promotion.values()
+                .stream());
+    }
+
+    public int getDiscount() {
+        return Util.intStreamSum(promotion.entrySet()
+                .stream()
+                .filter(event -> event.getKey() != Event.GIFT)
+                .map(Map.Entry::getValue));
+    }
+
+    public void setDiscountInfo(Map<Event, Integer> discountInfo) {
+        promotion.putAll(discountInfo);
+    }
+}

리뷰: Promotion이라는 클래스명은 클래스명만 보고 어떤 기능을 하는지 추측하기 어려운 것 같아요
@@ -0,0 +1,42 @@
+package christmas.model;
+
+import christmas.config.Event;
+import christmas.util.Util;
+
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class Promotion {
+    private final Map<Event, Integer> promotion;
+
+    public Promotion() {
+        promotion = new EnumMap<>(Event.class);
+    }
+
+    public Map<String, Integer> getPromotionList() {
+        return promotion.entrySet()
+                .stream()
+                .filter(event -> event.getValue() > 0)
+                .collect(Collectors.toMap(
+                        event -> event.getKey().getName(),
+                        Map.Entry::getValue)
+                );
+    }
+
+    public int getTotalDiscount() {
+        return Util.intStreamSum(promotion.values()
+                .stream());
+    }
+
+    public int getDiscount() {
+        return Util.intStreamSum(promotion.entrySet()
+                .stream()
+                .filter(event -> event.getKey() != Event.GIFT)
+                .map(Map.Entry::getValue));
+    }
+
+    public void setDiscountInfo(Map<Event, Integer> discountInfo) {
+        promotion.putAll(discountInfo);
+    }
+}

리뷰: 총 혜택 금액을 구하는 메서드인데 TotalDiscount는 총 할인 금액을 구하는 의미가 더 강한 것 같아서 메서드 이름을 getTotalBenefitPrice라던가 다른 이름으로 바꾸면 더 좋을 것 같아요
@@ -0,0 +1,42 @@
+package christmas.model;
+
+import christmas.config.Event;
+import christmas.util.Util;
+
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class Promotion {
+    private final Map<Event, Integer> promotion;
+
+    public Promotion() {
+        promotion = new EnumMap<>(Event.class);
+    }
+
+    public Map<String, Integer> getPromotionList() {
+        return promotion.entrySet()
+                .stream()
+                .filter(event -> event.getValue() > 0)
+                .collect(Collectors.toMap(
+                        event -> event.getKey().getName(),
+                        Map.Entry::getValue)
+                );
+    }
+
+    public int getTotalDiscount() {
+        return Util.intStreamSum(promotion.values()
+                .stream());
+    }
+
+    public int getDiscount() {
+        return Util.intStreamSum(promotion.entrySet()
+                .stream()
+                .filter(event -> event.getKey() != Event.GIFT)
+                .map(Map.Entry::getValue));
+    }
+
+    public void setDiscountInfo(Map<Event, Integer> discountInfo) {
+        promotion.putAll(discountInfo);
+    }
+}

리뷰: setter를 이용해서 Promotion 객체가 가진 상태를 조작하는 것은 별로 객체 지향적인 설계가 아닌 것 같아요
@@ -0,0 +1,49 @@
+package christmas.model;
+
+import christmas.config.MenuType;
+
+public class PromotionGenerator {
+    public static final int DISCOUNT_LIMIT_PRICE = 10000;
+
+    private final Promotion promotion;
+
+    public PromotionGenerator(final VisitDate visitDate, final Order order) {
+        this.promotion = new Promotion();
+
+        if (isDiscountTarget(order.getTotalPrice())) {
+            christmasDDayEvent(visitDate.getVisitDate());
+            weekdayEvent(visitDate.getWeekDay(), order.getMenuQuantity(MenuType.DESSERT));
+            weekendEvent(visitDate.getWeekDay(), order.getMenuQuantity(MenuType.MAIN));
+            specialDayEvent(visitDate.getVisitDate());
+            giftEvent(order.getTotalPrice());
+        }
+    }
+
+    private boolean isDiscountTarget(int totalPrice) {
+        return totalPrice >= DISCOUNT_LIMIT_PRICE;
+    }
+
+    private void christmasDDayEvent(int visitDate) {
+        promotion.setDiscountInfo(new ChristmasDDayEvent(visitDate).getDiscountInfo());
+    }
+
+    private void weekdayEvent(int weekday, int dessertQuantity) {
+        promotion.setDiscountInfo(new WeekdayEvent(weekday, dessertQuantity).getDiscountInfo());
+    }
+
+    private void weekendEvent(int weekday, int mainQuantity) {
+        promotion.setDiscountInfo(new WeekendEvent(weekday, mainQuantity).getDiscountInfo());
+    }
+
+    private void specialDayEvent(int visitDate) {
+        promotion.setDiscountInfo(new SpecialDayEvent(visitDate).getDiscountInfo());
+    }
+
+    private void giftEvent(int totalPrice) {
+        promotion.setDiscountInfo(new GiftEvent(totalPrice).getDiscountInfo());
+    }
+
+    public Promotion getPromotion() {
+        return promotion;
+    }
+}

리뷰: 이벤트 결과를 구하고 그 결과로 Promotion클래스를 생성하는 역할을 하는 것 같군요.
저는 Promotion을 굳이 생성하지 않고 이 클래스에서 관리하는게 더 좋다고 생각하는데 이렇게 설계하신 이유가 있을까요?
@@ -0,0 +1,49 @@
+package christmas.model;
+
+import christmas.config.MenuType;
+
+public class PromotionGenerator {
+    public static final int DISCOUNT_LIMIT_PRICE = 10000;
+
+    private final Promotion promotion;
+
+    public PromotionGenerator(final VisitDate visitDate, final Order order) {
+        this.promotion = new Promotion();
+
+        if (isDiscountTarget(order.getTotalPrice())) {
+            christmasDDayEvent(visitDate.getVisitDate());
+            weekdayEvent(visitDate.getWeekDay(), order.getMenuQuantity(MenuType.DESSERT));
+            weekendEvent(visitDate.getWeekDay(), order.getMenuQuantity(MenuType.MAIN));
+            specialDayEvent(visitDate.getVisitDate());
+            giftEvent(order.getTotalPrice());
+        }
+    }
+
+    private boolean isDiscountTarget(int totalPrice) {
+        return totalPrice >= DISCOUNT_LIMIT_PRICE;
+    }
+
+    private void christmasDDayEvent(int visitDate) {
+        promotion.setDiscountInfo(new ChristmasDDayEvent(visitDate).getDiscountInfo());
+    }
+
+    private void weekdayEvent(int weekday, int dessertQuantity) {
+        promotion.setDiscountInfo(new WeekdayEvent(weekday, dessertQuantity).getDiscountInfo());
+    }
+
+    private void weekendEvent(int weekday, int mainQuantity) {
+        promotion.setDiscountInfo(new WeekendEvent(weekday, mainQuantity).getDiscountInfo());
+    }
+
+    private void specialDayEvent(int visitDate) {
+        promotion.setDiscountInfo(new SpecialDayEvent(visitDate).getDiscountInfo());
+    }
+
+    private void giftEvent(int totalPrice) {
+        promotion.setDiscountInfo(new GiftEvent(totalPrice).getDiscountInfo());
+    }
+
+    public Promotion getPromotion() {
+        return promotion;
+    }
+}

리뷰: 26번 라인 부터 44번 라인은 각각의 이벤트를 적용하고 혜택 금액을 promotion객체에 set 해주는 것 같은데 이부분을 조금 더 간략화 할 수 있지 않을까요?
@@ -0,0 +1,49 @@
+package christmas.controller;
+
+import christmas.config.Constant;
+import christmas.config.EventBadge;
+import christmas.config.Gift;
+import christmas.model.Order;
+import christmas.model.Promotion;
+import christmas.model.PromotionGenerator;
+import christmas.model.VisitDate;
+import christmas.view.OutputView;
+
+public class PromotionController {
+    private final VisitDate visitDate;
+    private final Order order;
+    private final Promotion promotion;
+
+    public PromotionController() {
+        visitDate = InputController.setVisitDate();
+        order = InputController.setOrder();
+        promotion = new PromotionGenerator(visitDate, order).getPromotion();
+    }
+
+    public void active() {
+        print();
+    }
+
+    private int totalPrice() {
+        return order.getTotalPrice();
+    }
+
+    private int totalDiscount() {
+        return promotion.getTotalDiscount();
+    }
+
+    private boolean isEmptyPromotion() {
+        return promotion.getTotalDiscount() == 0;
+    }
+
+    private void print() {
+        OutputView.printVisitDate(visitDate.getVisitDate());
+        OutputView.printOrder(order.getOrderList());
+        OutputView.printTotalPrice(totalPrice());
+        OutputView.printGift(Gift.getGift(totalPrice()));
+        OutputView.printPromotion(isEmptyPromotion(), promotion.getPromotionList());
+        OutputView.printTotalDiscount(totalDiscount() * Constant.MINUS);
+        OutputView.printPaymentPrice(totalPrice() - promotion.getDiscount());
+        OutputView.printBadge(EventBadge.findBadgeByTotalDiscount(totalDiscount()));
+    }
+}

리뷰: 일반 적으로 Controller에서는 동작만 정의하지 상태는 정의하지 않는 걸로 알고 있는데, PromotionController는 Controller보다는 도메인스러운(?)것 같아요
@@ -0,0 +1,26 @@
+package christmas.config;
+
+import java.util.Arrays;
+
+public enum EventBadge {
+    SANTA("산타", 20000),
+    TREE("트리", 10000),
+    STAR("별", 5000),
+    NONE("없음", 0);
+
+    private final String name;
+    private final int limitPrice;
+
+    EventBadge(String name, int limitPrice) {
+        this.name = name;
+        this.limitPrice = limitPrice;
+    }
+
+    public static String findBadgeByTotalDiscount(int totalDiscount) {
+        return Arrays.stream(values())
+                .filter(badge -> totalDiscount >= badge.limitPrice)
+                .findFirst()
+                .orElse(NONE)
+                .name;
+    }
+}

리뷰: 밍굴이님 코드 보고 알게되었지만 Predicate 를 이용해서 조건을 걸어주면 좋았을 것 같습니다
함수형 인터페이스에 대한 공부를 해야겠네요!
@@ -0,0 +1,25 @@
+package christmas.config;
+
+public class Message {
+    public static final String INPUT_DATE = "12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)";
+    public static final String INPUT_ORDER = "주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)";
+
+    public static final String OUTPUT_VISIT_DATE = "12월 %d일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!\n";
+    public static final String OUTPUT_MENU_TITLE = "\n<주문 메뉴>";
+    public static final String OUTPUT_ITEM = "%s %d개%n";
+    public static final String OUTPUT_WON = "%s원%n";
+    public static final String OUTPUT_MINUS_WON = "-" + OUTPUT_WON;
+    public static final String OUTPUT_TOTAL_PRICE = "%n<할인 전 총주문 금액>%n" + OUTPUT_WON;
+    public static final String OUTPUT_GIFT_TITLE = "\n<증정 메뉴>";
+    public static final String OUTPUT_PROMOTION_TITLE = "\n<혜택 내역>";
+    public static final String OUTPUT_PROMOTION_ITEM = "%s: " + OUTPUT_MINUS_WON;
+    public static final String OUTPUT_TOTAL_DISCOUNT = "%n<총혜택 금액>%n" + OUTPUT_WON;
+    public static final String OUTPUT_PAYMENT_PRICE = "%n<할인 후 예상 결제 금액>%n" + OUTPUT_WON;
+    public static final String OUTPUT_BADGE = "%n<12월 이벤트 배지>%n%s%n";
+    public static final String OUTPUT_NONE = "없음";
+
+
+    public static final String ERROR_MESSAGE_PREFIX = "[ERROR]";
+    public static final String ERROR_INPUT_DATE = ERROR_MESSAGE_PREFIX + " 유효하지 않은 날짜입니다. 다시 입력해 주세요.";
+    public static final String ERROR_INPUT_ORDER = ERROR_MESSAGE_PREFIX + " 유효하지 않은 주문입니다. 다시 입력해 주세요.";
+}

리뷰: 출력부분의 메시지 상수의 경우에는 개발하면서 항상 고민하는 부분입니다. 코드를 한 군데 모아 가독성을 높일지, 분리해서 메시지를 조금도 유연하게 수정 가능하게 할지 고민하지만 대부분 출력에서는 정해진 규격의 정제된 데이터를 보내준다고 생각하여 변경가능성이 큰 메시지 자체를 분리하는 쪽을 선택했습니다.
저도 분리하는데 동의합니다! 처음에는 에러메시지를 다루는 클래스를 별도로 둘까 했는데 생각보다 적은 에러메시지 사용으로 이번에는 Message 클래스 내에 합쳐서 사용을 했습니다!
@@ -0,0 +1,25 @@
+package christmas.controller;
+
+import christmas.model.Order;
+import christmas.model.VisitDate;
+import christmas.view.InputView;
+
+public class InputController {
+    public static VisitDate setVisitDate() {
+        try {
+            return new VisitDate(InputView.readDate());
+        } catch (IllegalArgumentException illegalArgumentException) {
+            System.out.println(illegalArgumentException.getMessage());
+            return setVisitDate();
+        }
+    }
+
+    public static Order setOrder() {
+        try {
+            return new Order(InputView.readOrder());
+        } catch (IllegalArgumentException illegalArgumentException) {
+            System.out.println(illegalArgumentException.getMessage());
+            return setOrder();
+        }
+    }
+}

리뷰: 함수명 짓기에 관련해서는 이번 프로젝트에서 많이 부족했네요...
@@ -0,0 +1,49 @@
+package christmas.model;
+
+import christmas.config.MenuType;
+
+public class PromotionGenerator {
+    public static final int DISCOUNT_LIMIT_PRICE = 10000;
+
+    private final Promotion promotion;
+
+    public PromotionGenerator(final VisitDate visitDate, final Order order) {
+        this.promotion = new Promotion();
+
+        if (isDiscountTarget(order.getTotalPrice())) {
+            christmasDDayEvent(visitDate.getVisitDate());
+            weekdayEvent(visitDate.getWeekDay(), order.getMenuQuantity(MenuType.DESSERT));
+            weekendEvent(visitDate.getWeekDay(), order.getMenuQuantity(MenuType.MAIN));
+            specialDayEvent(visitDate.getVisitDate());
+            giftEvent(order.getTotalPrice());
+        }
+    }
+
+    private boolean isDiscountTarget(int totalPrice) {
+        return totalPrice >= DISCOUNT_LIMIT_PRICE;
+    }
+
+    private void christmasDDayEvent(int visitDate) {
+        promotion.setDiscountInfo(new ChristmasDDayEvent(visitDate).getDiscountInfo());
+    }
+
+    private void weekdayEvent(int weekday, int dessertQuantity) {
+        promotion.setDiscountInfo(new WeekdayEvent(weekday, dessertQuantity).getDiscountInfo());
+    }
+
+    private void weekendEvent(int weekday, int mainQuantity) {
+        promotion.setDiscountInfo(new WeekendEvent(weekday, mainQuantity).getDiscountInfo());
+    }
+
+    private void specialDayEvent(int visitDate) {
+        promotion.setDiscountInfo(new SpecialDayEvent(visitDate).getDiscountInfo());
+    }
+
+    private void giftEvent(int totalPrice) {
+        promotion.setDiscountInfo(new GiftEvent(totalPrice).getDiscountInfo());
+    }
+
+    public Promotion getPromotion() {
+        return promotion;
+    }
+}

리뷰: 처음 설계에서는 Promotion클래스에서 이벤트 결과를 구하고 상태를 저장하는 형태를 가지고 있었는데
필요 이상으로 Promotion클래스가 비대해졌다고 생각을 해서 이를 이벤트 결과를 구하는 PromotionGenerator클래스와 이벤트 결과를 처리하는 Promotion 클래스로 분리했습니다. 이 과정에서 Promotion의 상태를 조작하는 setter를 사용하게 되었는데 오히려 객체 지향적이지 못한 설계가 되었네요ㅠㅠ
@@ -0,0 +1,49 @@
+package christmas.controller;
+
+import christmas.config.Constant;
+import christmas.config.EventBadge;
+import christmas.config.Gift;
+import christmas.model.Order;
+import christmas.model.Promotion;
+import christmas.model.PromotionGenerator;
+import christmas.model.VisitDate;
+import christmas.view.OutputView;
+
+public class PromotionController {
+    private final VisitDate visitDate;
+    private final Order order;
+    private final Promotion promotion;
+
+    public PromotionController() {
+        visitDate = InputController.setVisitDate();
+        order = InputController.setOrder();
+        promotion = new PromotionGenerator(visitDate, order).getPromotion();
+    }
+
+    public void active() {
+        print();
+    }
+
+    private int totalPrice() {
+        return order.getTotalPrice();
+    }
+
+    private int totalDiscount() {
+        return promotion.getTotalDiscount();
+    }
+
+    private boolean isEmptyPromotion() {
+        return promotion.getTotalDiscount() == 0;
+    }
+
+    private void print() {
+        OutputView.printVisitDate(visitDate.getVisitDate());
+        OutputView.printOrder(order.getOrderList());
+        OutputView.printTotalPrice(totalPrice());
+        OutputView.printGift(Gift.getGift(totalPrice()));
+        OutputView.printPromotion(isEmptyPromotion(), promotion.getPromotionList());
+        OutputView.printTotalDiscount(totalDiscount() * Constant.MINUS);
+        OutputView.printPaymentPrice(totalPrice() - promotion.getDiscount());
+        OutputView.printBadge(EventBadge.findBadgeByTotalDiscount(totalDiscount()));
+    }
+}

리뷰: MVC패턴에 대한 내용을 프리코스를 시작하면서 접했는데 얄팍한 지식이 잘못된 코드를 만들었네요 ㅎㅎ;;
다시 공부해보겠습니다!
@@ -0,0 +1,27 @@
+package christmas.domain.policy.discount;
+
+import christmas.domain.Orders;
+import christmas.domain.VisitDate;
+
+public class DdayDiscountPolicy implements DiscountPolicy {
+    public static final int CHRISTMAS_DATE = 25;
+    public static final int DISCOUNT_AMOUNT = 1000;
+    public static final int ADDITIONAL_DISCOUNT_AMOUNT = 100;
+    public static final int NOT_DISCOUNT_AMOUNT = 0;
+
+    @Override
+    public int discount(VisitDate visitDate, Orders orders) {
+        if (isNotAfter(visitDate)) {
+            return calculateDiscount(visitDate);
+        }
+        return NOT_DISCOUNT_AMOUNT;
+    }
+
+    private boolean isNotAfter(VisitDate visitDate) {
+        return visitDate.isNotAfter(CHRISTMAS_DATE);
+    }
+
+    private int calculateDiscount(VisitDate visitDate) {
+        return  DISCOUNT_AMOUNT + visitDate.calculateDiscount(ADDITIONAL_DISCOUNT_AMOUNT);
+    }
+}

리뷰: DdayDiscountPolicy의 discount 메소드가 단독으로 사용될 수 도 있는데 만원 이하면 적용이 불가능하다는 조건에 안전하지 않은 것 같습니다. DiscountPolicy를 추상클래스로 하여 만원 이하 적용 불가 조건을 걸면 어떨까요?
@@ -0,0 +1,53 @@
+package christmas.domain;
+
+import christmas.dto.BenefitDto;
+import christmas.dto.GiftsDto;
+import java.util.function.Supplier;
+
+public class Bill {
+    public static final int MINIMUM_ORDERS_PRICE = 10000;
+    public static final int EVENT_NOT_APPLIED_AMOUNT = 0;
+
+    private final Discounts discounts;
+    private final Gifts gifts;
+
+    public Bill(Discounts discounts, Gifts gifts) {
+        this.discounts = discounts;
+        this.gifts = gifts;
+    }
+
+    public int getTotalBenefit(Orders orders) {
+        return getAmount(this::sumBenefit, orders);
+    }
+
+    public int getDiscountPrice(Orders orders) {
+        return orders.getTotalPrice() - discounts.sumDiscount();
+    }
+
+    public GiftsDto getGiftDto() {
+        return new GiftsDto(gifts.getResult());
+    }
+
+    public BenefitDto getBenefitDto() {
+        return new BenefitDto(discounts.getResult(), gifts.sumPrice());
+    }
+
+    public String getBadgeName() {
+        return Badge.getNameByBenefit(sumBenefit());
+    }
+
+    private int sumBenefit() {
+        return discounts.sumDiscount() + gifts.sumPrice();
+    }
+
+    private boolean isEventApplicable(Orders orders) {
+        return orders.getTotalPrice() >= MINIMUM_ORDERS_PRICE;
+    }
+
+    private <T> int getAmount(Supplier<T> supplier, Orders orders) {
+        if (isEventApplicable(orders)) {
+            return (int) supplier.get();
+        }
+        return EVENT_NOT_APPLIED_AMOUNT;
+    }
+}

리뷰: domain이 특정 Dto에 종속되는 것에 어떻게 생각하시나요? 저는 Dto는 변동성이 큰 객체라고 생각해서 domain이 의존하는 형태는 좋지 않다고 생각합니다.
@@ -0,0 +1,36 @@
+package christmas.domain;
+
+import christmas.exception.NotExistsBadgeException;
+import java.util.Arrays;
+
+public enum Badge {
+    SANTA("산타", 20000),
+    TREE("트리", 10000),
+    STAR("별", 5000),
+    NONE("없음", 0),
+    ;
+
+    private final String name;
+    private final int minimumAmount;
+
+    private Badge(String name, int minimumAmount) {
+        this.name = name;
+        this.minimumAmount = minimumAmount;
+    }
+
+    public static String getNameByBenefit(int amount) {
+        return from(amount).name;
+    }
+
+    public static Badge from(int amount) {
+        return Arrays.stream(Badge.values())
+                .filter(badge -> badge.isGreaterThan(amount))
+                .findFirst()
+                .orElseThrow(NotExistsBadgeException::new);
+    }
+
+    private boolean isGreaterThan(int benefitAmount) {
+        return this.minimumAmount <= benefitAmount;
+    }
+}
+

리뷰: enum의 순서가 고정되어야 정상실행되는 코드라고 생각합니다. 다른 등급이 추가되거나 했을 때 영향이 없도록 minimumAmount로 정렬을 하는 것은 어떨까요?