ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,18 @@
+package christmas.domain;
+
+public enum DiscountType {
+    CHRISTMAS_DDAY("크리스마스 디데이 할인"),
+    WEEKDAY("평일 할인"),
+    WEEKEND("주말 할인"),
+    SPECIAL("특별 할인");
+
+    private final String name;
+
+    DiscountType(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

리뷰: 이미 Policies로 클래스별 구분이 되어 있는데 enum으로 제목만 가지고 있을 필요가 있을까요? 제목을 각 Policy 내부로 넣는 것도 괜찮을 거 같습니다.
@@ -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;
+    }
+}

리뷰: 저도 동감합니다 🤔 

사실 저도 도메인에 toDto 메서드를 만들어서 구현하긴 했어요 
DTO에서든 Mapper를 통해서든 다른 방식으로 DTO를 생성하려면 getter를 열어야 하는데 getter를 최대한 지양해볼려고 그렇게 했습니다. 

그런데 프리코스나 다른 글들 보니 많은 분들이 중요한 객체가(도메인) 덜 중요한 객체(DTO)를 의존하는 것이 좋지 않다고 말씀하시더라고요 그래서 저도 DTO의 생성방식에 대해서 새롭게 고민하게 되었네요
@@ -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;
+    }
+}

리뷰: 10000원 이상이여야만 이벤트가 적용되는 공통 조건을 Bill에 위치시키셨네요..!

해당 조건이 이벤트를 적용할지 말지를 정한다는 측면에서 Bill에 위치하기에는 책임이 벗어난 느낌이 있는 것 같습니다. 각 이벤트를 클래스로 구현하신 것 같은데 10000원 이상인지 검증하는 코드의 위치는 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;
+    }
+}

리뷰: 할인된 가격을 구하는 메서드의 위치는 Orders가 적절할까요 Bill이 적절할까요 🤔 

저도 이것을 참 많이 고민했는데요 ..! 저는 Order에 위치시키긴 했는데 관련해서 하늘님 생각도 궁금합니다!

또 Orders라는 도메인을 파라미터로 받고 있는데 int를 받는 것은 어떻게 생각하시는지도 궁금해요 관련한 기준 공유해주시면 감사하겠습니다 🙇🏻
@@ -0,0 +1,25 @@
+package christmas.domain;
+
+import java.util.Map;
+
+public class Discount {
+    private final DiscountType type;
+    private final int amount;
+
+    public Discount(DiscountType type, int amount) {
+        this.type = type;
+        this.amount = amount;
+    }
+
+    public boolean isDiscount() {
+        return amount != 0;
+    }
+
+    public int getAmount() {
+        return amount;
+    }
+
+    public String getName() {
+        return type.getName();
+    }
+}

리뷰: 할인내역은 할인 종류와 할인 금액의 양으로 정의할 수 있군요 👍🏻 

저도 비슷하게 작성했지만 저는 String 타입의 description과 int amount로 작성했네요.. 
이렇게 보니 enum으로 관리하신 것 정말 좋아보이네요 세세한 코드 잘 배우고 갑니다 👍🏻 👍🏻
@@ -0,0 +1,25 @@
+package christmas.domain;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class Discounts {
+    private final List<Discount> discounts;
+
+    public Discounts(List<Discount> discounts) {
+        this.discounts = discounts;
+    }
+
+    public int sumDiscount() {
+        return discounts.stream()
+                .mapToInt(Discount::getAmount)
+                .sum();
+    }
+
+    public Map<String, Integer> getResult() {
+        return discounts.stream()
+                .filter(Discount::isDiscount)
+                .collect(Collectors.toUnmodifiableMap(Discount::getName, Discount::getAmount));
+    }
+}

리뷰: 일급 컬렉션을 추출하시고 관련된 로직도 깔끔하게 잘 모아주신 것 같아요 👍🏻 
좋아보입니다 😄
@@ -0,0 +1,21 @@
+package christmas.domain;
+
+public enum GiftType {
+    CHAMPAGNE("샴페인", 25_000);
+
+    private final String name;
+    private final int price;
+
+    private GiftType(String name, int price) {
+        this.name = name;
+        this.price = price;
+    }
+
+    public int calculatePrice(int quantity) {
+        return this.price * quantity;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

리뷰: 메뉴이기도 하면서 증정품이기도 한 샴페인은 GiftType에도 위치하고 Menu에도 위치하네요 🤔 
변경 요구사항으로 샴페인의 판매가격이 수정된다면 해당 위치도 수정되어야 할 것 같아서 수정 지점이 퍼지는 부분이 있는 것 같습니다!
@@ -0,0 +1,57 @@
+package christmas.domain;
+
+import static christmas.domain.MenuType.APPETIZER;
+import static christmas.domain.MenuType.DESSERT;
+import static christmas.domain.MenuType.DRINK;
+import static christmas.domain.MenuType.MAIN;
+
+import christmas.exception.InvalidOrderException;
+import java.util.Arrays;
+
+public enum Menu {
+    MUSHROOM_SOUP("양송이수프", 6_000, APPETIZER),
+    TAPAS("타파스", 5_500, APPETIZER),
+    CAESAR_SALAD("시저샐러드", 8_000, APPETIZER),
+
+    T_BONE_STEAK("티본스테이크", 55_000, MAIN),
+    BBQ_RIBS("바비큐립", 54_000, MAIN),
+    SEAFOOD_PASTA("해산물파스타", 35_000, MAIN),
+    CHRISTMAS_PASTA("크리스마스파스타", 25_000, MAIN),
+
+    CHOCOLATE_CAKE("초코케이크", 15_000, DESSERT),
+    ICE_CREAM("아이스크림", 5_000, DESSERT),
+
+    ZERO_COLA("제로콜라", 3_000, DRINK),
+    RED_WINE("레드와인", 60_000, DRINK),
+    CHAMPAGNE("샴페인", 25_000, DRINK);
+
+    private final String name;
+    private final int price;
+    private final MenuType type;
+
+    Menu(String menuName, int price, MenuType type) {
+        this.name = menuName;
+        this.price = price;
+        this.type = type;
+    }
+
+    public static Menu from(String menuName) {
+        return Arrays.stream(Menu.values())
+                .filter(menu -> menuName.equals(menu.name))
+                .findFirst()
+                .orElseThrow(InvalidOrderException::new);
+    }
+
+    public boolean isEqualsMenuType(MenuType menuType) {
+        return this.type == menuType;
+    }
+
+    public int calculatePrice(int quantity) {
+        return this.price * quantity;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
+

리뷰: 게터를 최대한 지양하셨네요..! 저는 이렇게 까지 생각 못해보고 enum에는 Getter를 전부 열었네요 😭 
좋아보입니다! 👍🏻
@@ -0,0 +1,50 @@
+package christmas.domain;
+
+import static christmas.exception.ErrorMessages.INVALID_ORDER;
+
+import christmas.utils.IntegerConverter;
+import christmas.exception.InvalidOrderException;
+
+public class Order {
+    public static final int MIN_ORDER_QUANTITY = 1;
+
+    private final Menu menu;
+    private final int quantity;
+
+    public Order(String menuName, String quantity) {
+        this.menu = Menu.from(menuName);
+        this.quantity = convertType(quantity);
+
+        validateMinQuantity();
+    }
+
+    private int convertType(String quantity) {
+        return IntegerConverter.convert(quantity, INVALID_ORDER);
+    }
+
+    private void validateMinQuantity() {
+        if (quantity < MIN_ORDER_QUANTITY) {
+            throw new InvalidOrderException();
+        }
+    }
+
+    public boolean isEqualsMenuType(MenuType type) {
+        return menu.isEqualsMenuType(type);
+    }
+
+    public int calculatePrice() {
+        return menu.calculatePrice(quantity);
+    }
+
+    public String getMenuName() {
+        return menu.getName();
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public Menu getMenu() {
+        return menu;
+    }
+}

리뷰: String으로 들어온 quantity를 정수로 컨버팅 하고 생성자를 통해 할당이 가능하도록 변환해주는 부분이네요!

도메인 관련 로직이 아님에도 Order 도메인에 위치하여 Order 도메인의 책임을 흐릴 수도 있다고 생각하는데 어떻게 생각하시나요 🤔 변환은 외부에서 일어나고 생성요청을 받을 때에는 정제된 상태로 받아야 하지 않나라는 개인적인 생각입니다 👍🏻
@@ -0,0 +1,104 @@
+package christmas.domain;
+
+import christmas.dto.OrdersDto;
+import christmas.exception.InvalidOrderException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class Orders {
+    public static final String ORDER_DELIMITER = ",";
+    public static final String MENU_AND_QUANTITY_DELIMITER = "-";
+    public static final int MAX_ORDER_QUANTITY = 20;
+
+    private final List<Order> orders;
+
+    public Orders(String orders) {
+        validateFormat(orders);
+        this.orders = createOrders(orders);
+
+        validateOverQuantity();
+        validateDuplicate();
+        validateAllDrink();
+    }
+
+    private List<Order> createOrders(String value) {
+        return getOrdersStream(value)
+                .map(order -> new Order(order[0], order[1]))
+                .toList();
+    }
+
+    private void validateFormat(String value) {
+        if (isInvalidFormat(value)) {
+            throw new InvalidOrderException();
+        }
+    }
+
+    private boolean isInvalidFormat(String value) {
+        return getOrdersStream(value)
+                .anyMatch(order -> order.length != 2);
+    }
+
+    private Stream<String[]> getOrdersStream(String value) {
+        return Arrays.stream(value.split(ORDER_DELIMITER))
+                .map(order -> order.split(MENU_AND_QUANTITY_DELIMITER));
+    }
+
+    private void validateOverQuantity() {
+        if (sumOrderQuantity() > MAX_ORDER_QUANTITY) {
+            throw new InvalidOrderException();
+        }
+    }
+
+    private int sumOrderQuantity() {
+        return orders.stream()
+                .mapToInt(Order::getQuantity)
+                .sum();
+    }
+
+    private void validateDuplicate() {
+        if (countUniqueOrderMenu() != orders.size()) {
+            throw new InvalidOrderException();
+        }
+    }
+
+    private int countUniqueOrderMenu() {
+        return (int) orders.stream()
+                .map(Order::getMenu)
+                .distinct()
+                .count();
+    }
+
+    private void validateAllDrink() {
+        if (isAllDrinkMenu()) {
+            throw new InvalidOrderException();
+        }
+    }
+
+    private boolean isAllDrinkMenu() {
+        return orders.stream()
+                .allMatch(order -> order.isEqualsMenuType(MenuType.DRINK));
+    }
+
+    public int getTotalPrice() {
+        return orders.stream()
+                .mapToInt(Order::calculatePrice)
+                .sum();
+    }
+
+    public int getQuantityByMenuType(MenuType type) {
+        return orders.stream()
+                .filter(order -> order.isEqualsMenuType(type))
+                .mapToInt(Order::getQuantity)
+                .sum();
+    }
+
+    public OrdersDto getOrders() {
+        Map<String, Integer> result = orders.stream()
+                .collect(Collectors.toUnmodifiableMap(Order::getMenuName, Order::getQuantity));
+
+        return new OrdersDto(result);
+    }
+}

리뷰: 바로 위와 마찬가지로 String을 기반으로 생성하려고 하다보니 String을 split하고 값을 파싱하는 등의 책임이 Orders 도메인에 위치하도록 유도하고 있네요! 

List<Order>를 받아서 바로 생성하도록 유도하시는 것도 좋아보입니다 😄
@@ -0,0 +1,48 @@
+package christmas.domain;
+
+import christmas.exception.InvalidDateException;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.List;
+
+public class VisitDate {
+    private static final int YEAR = 2023;
+    public static final int MONTH = 12;
+    private static final int MIN_DATE = 1;
+    private static final int MAX_DATE = 31;
+
+    private final int date;
+
+    public VisitDate(int date) {
+        validateDateRange(date);
+        this.date = date;
+    }
+
+    private void validateDateRange(int date) {
+        if (date < MIN_DATE || date > MAX_DATE) {
+            throw new InvalidDateException();
+        }
+    }
+
+    public DayOfWeek getDayOfWeek() {
+        LocalDate localDate = LocalDate.of(YEAR, MONTH, date);
+        return localDate.getDayOfWeek();
+    }
+
+    public boolean isContainDayOfDay(List<DayOfWeek> weekDay) {
+        DayOfWeek dayOfWeek = getDayOfWeek();
+        return weekDay.contains(dayOfWeek);
+    }
+
+    public boolean isEqualsDate(int date) {
+        return this.date == date;
+    }
+
+    public boolean isNotAfter(int date) {
+        return this.date <= date;
+    }
+
+    public int calculateDiscount(int discountAmount) {
+        return (date - 1) * discountAmount;
+    }
+}

리뷰: 주말인지 평일인지 물어보기 위해서 getDayOfWeek를 사용하신 것 같은데 isWeekend()와 같은 메서드가 TDA 측면에서 좋아보인다는 생각입니다 😄
@@ -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);
+    }
+}

리뷰: 추상 클래스 전환 너무 좋은 아이디어인거 같습니다. 그리고 좀 더 찾아보니 인터페이스에 default 메소드를 구현할 수 있더라구요. 인터페이스 default 메소드를 구현해 조건을 검사할 수 있을거 같습니다. 이렇게 하면 인터페이스의 장점도 가져가고, 조건 검사의 책임도 부여할 수 있을거 같네요. 리뷰 덕분에 추상 클래스, 인터페이스에 대해 찾아보고 공부할 수 있었습니다 감사합니다 😊
@@ -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;
+    }
+}

리뷰: > Dto는 변동성이 큰 객체라고 생각해서 domain이 의존하는 형태는 좋지 않다고 생각합니다.  
> 중요한 객체가(도메인) 덜 중요한 객체(DTO)를 의존하는 것이 좋지 않다

너무 공감되는 내용이네요! 현재는 DTO와 Domain의 강한 결합도로 인해 DTO 변경 시 Domain 코드를 수정해야한다는 문제가 있는거 같아요. Domain에서는 DTO 생성에 필요한 값을 반환하고, Controller에서 DTO를 생성하는 것이 좋을거 같네요!!! 감사합니다
@@ -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 내림차순으로 상수를 선언했기 때문에 문제가 없지만, 추후 다른 사람이 해당 코드에 대한 추가적인 구현을 하는 경우 상수 선언 규칙을 인지하지 못할 수 있을거 같네요. @Arachneee 말씀처럼 어떠한 상황에서도 코드의 동작을 명확하게 하기 위해 정렬하는 코드를 추가하는 것이 좋을거 같습니다. 좋은 리뷰 감사합니다!
@@ -0,0 +1,18 @@
+package christmas.domain;
+
+public enum DiscountType {
+    CHRISTMAS_DDAY("크리스마스 디데이 할인"),
+    WEEKDAY("평일 할인"),
+    WEEKEND("주말 할인"),
+    SPECIAL("특별 할인");
+
+    private final String name;
+
+    DiscountType(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

리뷰: 저도 각 DiscountPolicy 구현체에서 할인 제목을 반환하는 구조를 생각해봤는데, 이 구조는 DiscountPolicy와 할인 제목이 일대일로 매핑되기 때문에 DiscountPolicy를 여러 할인에서 재사용할 수 없다는 단점이 있었습니다. 추후 추가될 수 있는 요구사항을 고려했을 때 확장성 면에서 좋지 않을것이라 생각해 DiscountPolicy와 할인 제목을 따로 관리했습니다. 하지만, 어떤 코드가 더 좋은 코드인지 정답은 없는거 같아요. 이번 미션을 진행하며 요구사항 확장을 얼마나 고려해야할지에 대한 고민이 많았기에 제 생각을 한 번 공유드려봅니다!

추가로, Enum으로 할인 종류를 한 곳에서 관리하는 것이 가독성 면에서 더 좋다고 생각했어요. 또한, 할인 제목을 String으로 관리하면 잘못된 값이 들어갈 수 있기 때문에 Enum을 사용했습니다. 😀
@@ -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;
+    }
+}

리뷰: @Libienz 말씀처럼 Bill는 주문에 대한 할인 내역을 갖는 객체인데 해당 객체에서 할인 조건을 검사하는 것은 많은 책임을 갖고 있는거 같다는 생각이 드네요. DiscountPolicy의 default 메서드로 조건을 검사하는 것이 좋을거 같습니다 좋은 리뷰 감사합니다 😀
@@ -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;
+    }
+}

리뷰: Bill 객체는 Order와 Discount, Gift 객체 사이의 협력을 위한 중간 객체로, 주문에 대한 할인 혜택을 반환하기 위해 설계했어요. 따라서, 할인 후 예상 결제 금액에 대한 계산은 Bill 객체의 책임이라고 생각했습니다. 코드를 다시 살펴보니 아래와 같이 Order 객체에 할인 금액을 넘겨줌으로써 Order 객체 내부에서 할인된 가격을 반환하는 방법도 있을거 같습니다. 아래 코드가 더 객체간 메시지를 주고받으며 협력하는 구조라고 생각되네요. 덕분에 더 나은 코드로 리팩토링 할 수 있게 되었네요 감사합니다 😶

```java
public int getDiscountPrice(Orders orders) {
      int discount = discounts.sumDiscount();
      return orders.getDiscountPrice(discount);
  }
```

그리고, Bill 객체는 중간 객체이기 때문에 Orders 도메인을 알아도 된다고 생각했어요. 만약, Orders가 아닌 총 주문 금액을 인자로 받는다면 외부에서 getTotalPrice 메소드를 호출해야하는데, 외부에서 총 주문 금액을 알고있어야 할까? 라는 의문이 들었습니다. 따라서, Restaurant 객체는 방문 날짜와 주문을 받기만 하고, 이를 Bill 객체에게 할인된 가격을 알려줘 ~ 라고 책임을 위임하는 것이 더 객체지향스럽다고 생각했어요. @Libienz 리뷰를 통해 제 코드에 대한 근거를 다시 한번 생각해볼 수 있었네요 감사합니다 !!!!