ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,20 @@
+import { IsInt, IsNotEmpty, IsPositive, IsString, IsUUID } from 'class-validator';
+
+export class CreateReviewDTO {
+  @IsUUID()
+  @IsNotEmpty()
+  makerId: string;
+
+  @IsInt()
+  @IsPositive()
+  @IsNotEmpty()
+  rating: number;
+
+  @IsString()
+  @IsNotEmpty()
+  content: string;
+
+  @IsUUID()
+  @IsNotEmpty()
+  planId: string;
+}

리뷰: 별점에 0.5점은 없나요?
@@ -0,0 +1,20 @@
+import { IsInt, IsNotEmpty, IsPositive, IsString, IsUUID } from 'class-validator';
+
+export class CreateReviewDTO {
+  @IsUUID()
+  @IsNotEmpty()
+  makerId: string;
+
+  @IsInt()
+  @IsPositive()
+  @IsNotEmpty()
+  rating: number;
+
+  @IsString()
+  @IsNotEmpty()
+  content: string;
+
+  @IsUUID()
+  @IsNotEmpty()
+  planId: string;
+}

리뷰: 네! 값은 정수(1,2,3,4,5)로 받기로 하고, 응답에는 소수점 1자리까지 반환하기로 했습니다
@@ -0,0 +1,48 @@
+import { PlanReference } from 'src/common/types/plan/plan.type';
+import { ReviewAllProperties, ReviewProperties } from 'src/common/types/review/review.types';
+import { UserReference } from 'src/common/types/user/user.types';
+
+export default class Review {
+  private readonly id?: string;
+  private writerId: string;
+  private writer?: UserReference;
+  private ownerId: string;
+  private owner?: UserReference;
+  private rating: number;
+  private content: string;
+  private planId: string;
+  private plan: PlanReference;
+  private readonly createdAt?: Date;
+  private readonly updatedAt?: Date;
+
+  constructor(review: ReviewAllProperties) {
+    this.id = review?.id;
+    this.writerId = review.writerId;
+    this.writer = review?.writer;
+    this.ownerId = review.ownerId;
+    this.owner = review?.owner;
+    this.rating = review.rating;
+    this.content = review.content;
+    this.planId = review.planId;
+    this.plan = review?.plan;
+    this.createdAt = review?.createdAt;
+    this.updatedAt = review?.updatedAt;
+  }
+
+  static create(data: ReviewProperties) {
+    return new Review(data);
+  }
+
+  toDB() {
+    return {
+      id: this.id,
+      writerId: this.writerId,
+      ownerId: this.ownerId,
+      rating: this.rating,
+      content: this.content,
+      planId: this.planId,
+      createdAt: this.createdAt,
+      updatedAt: this.updatedAt
+    };
+  }
+}

리뷰: get 메소드로 DB보내기 보다는 DB에는 createdAt과 updatedAt을 쓸 일이 없으니까 뺴고 toDB 메소드 이름이 좋아보여요. 만약 모든 데이터가 필요한 부분이 생긴다면 그 때 get 메소드로 모든걸 내보내는건 어떨까요?
@@ -0,0 +1,45 @@
+import { Injectable } from '@nestjs/common';
+import ReviewRepository from './review.repository';
+import PlanService from '../plan/plan.service';
+import { StatusEnum } from 'src/common/constants/status.type';
+import BadRequestError from 'src/common/errors/badRequestError';
+import ErrorMessage from 'src/common/constants/errorMessage.enum';
+import { CreateReviewDTO } from 'src/common/types/review/review.dto';
+import Review from 'src/common/domains/review/review.domain';
+import { InjectQueue } from '@nestjs/bullmq';
+import { Queue } from 'bullmq';
+
+@Injectable()
+export default class ReviewService {
+  constructor(
+    @InjectQueue('stats') private readonly queue: Queue,
+    private readonly repository: ReviewRepository,
+    private readonly plan: PlanService
+  ) {}
+
+  async create(writerId: string, data: CreateReviewDTO) {
+    const plan = await this.plan.getPlanById(data.planId);
+    if (plan.status !== StatusEnum.COMPLETED) {
+      throw new BadRequestError(ErrorMessage.REVIEW_BAD_REQUEST);
+    }
+
+    const review = Review.create({
+      writerId,
+      ownerId: data.makerId,
+      rating: data.rating,
+      content: data.content,
+      planId: data.planId
+    });
+    await this.repository.create(review);
+
+    // 메이커의 UserStats(리뷰 수, 평점) 업데이트 작업을 큐에 추가
+    await this.queue.add('stats', {
+      userId: data.makerId,
+      event: EventType.REVIEW,
+      isAdd: true,
+      rating: data.rating
+    });
+
+    return;
+  }
+}

리뷰: 값을 넣어줄 뿐이더라도 유지보수 측면에서 static create 메소드로 만드시려는거 아니였나요?
@@ -0,0 +1,45 @@
+import { Injectable } from '@nestjs/common';
+import ReviewRepository from './review.repository';
+import PlanService from '../plan/plan.service';
+import { StatusEnum } from 'src/common/constants/status.type';
+import BadRequestError from 'src/common/errors/badRequestError';
+import ErrorMessage from 'src/common/constants/errorMessage.enum';
+import { CreateReviewDTO } from 'src/common/types/review/review.dto';
+import Review from 'src/common/domains/review/review.domain';
+import { InjectQueue } from '@nestjs/bullmq';
+import { Queue } from 'bullmq';
+
+@Injectable()
+export default class ReviewService {
+  constructor(
+    @InjectQueue('stats') private readonly queue: Queue,
+    private readonly repository: ReviewRepository,
+    private readonly plan: PlanService
+  ) {}
+
+  async create(writerId: string, data: CreateReviewDTO) {
+    const plan = await this.plan.getPlanById(data.planId);
+    if (plan.status !== StatusEnum.COMPLETED) {
+      throw new BadRequestError(ErrorMessage.REVIEW_BAD_REQUEST);
+    }
+
+    const review = Review.create({
+      writerId,
+      ownerId: data.makerId,
+      rating: data.rating,
+      content: data.content,
+      planId: data.planId
+    });
+    await this.repository.create(review);
+
+    // 메이커의 UserStats(리뷰 수, 평점) 업데이트 작업을 큐에 추가
+    await this.queue.add('stats', {
+      userId: data.makerId,
+      event: EventType.REVIEW,
+      isAdd: true,
+      rating: data.rating
+    });
+
+    return;
+  }
+}

리뷰: 저번에 그렇게 말씀드렸었죠..ㅎㅎㅎ 그런데 오늘 review 도메인을 새로 만들다보니 create 메소드에서 return new Revier(data)만 하고, 특별히 검증이나 다른 작업이 없어서 굳이 써야 할까 의문이 들어가지고... 다른 도메인도 도메인을 만들 때 특별한 이슈가 없다면 바꿀까 하는데, 어떻게 생각하시나요?
@@ -0,0 +1,48 @@
+import { PlanReference } from 'src/common/types/plan/plan.type';
+import { ReviewAllProperties, ReviewProperties } from 'src/common/types/review/review.types';
+import { UserReference } from 'src/common/types/user/user.types';
+
+export default class Review {
+  private readonly id?: string;
+  private writerId: string;
+  private writer?: UserReference;
+  private ownerId: string;
+  private owner?: UserReference;
+  private rating: number;
+  private content: string;
+  private planId: string;
+  private plan: PlanReference;
+  private readonly createdAt?: Date;
+  private readonly updatedAt?: Date;
+
+  constructor(review: ReviewAllProperties) {
+    this.id = review?.id;
+    this.writerId = review.writerId;
+    this.writer = review?.writer;
+    this.ownerId = review.ownerId;
+    this.owner = review?.owner;
+    this.rating = review.rating;
+    this.content = review.content;
+    this.planId = review.planId;
+    this.plan = review?.plan;
+    this.createdAt = review?.createdAt;
+    this.updatedAt = review?.updatedAt;
+  }
+
+  static create(data: ReviewProperties) {
+    return new Review(data);
+  }
+
+  toDB() {
+    return {
+      id: this.id,
+      writerId: this.writerId,
+      ownerId: this.ownerId,
+      rating: this.rating,
+      content: this.content,
+      planId: this.planId,
+      createdAt: this.createdAt,
+      updatedAt: this.updatedAt
+    };
+  }
+}

리뷰: 네! 서비스에 get 메서드 추가하면서 toDB, toClient 이런 식으로 수정하겠습니다 :)
@@ -0,0 +1,51 @@
+package christmas;
+
+import camp.nextstep.edu.missionutils.Console;
+import christmas.controller.EventController;
+import christmas.model.domain.event.strategy.ChristmasEventStrategy;
+import christmas.model.domain.event.EventRepository;
+import christmas.model.domain.event.strategy.EventStrategy;
+import christmas.model.domain.event.benefit.totaldiscount.ChristmasTotalDiscount;
+import christmas.model.service.ChristmasEventService;
+import christmas.model.util.Converter;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+
+public class AppConfig {
+
+    public EventController getChristmasEventController() {
+        return new EventController(christmasEventService(), inputView(), outputView());
+    }
+
+    private InputView inputView() {
+        return new InputView();
+    }
+
+    private OutputView outputView() {
+        return new OutputView();
+    }
+
+    private ChristmasEventService christmasEventService() {
+        return new ChristmasEventService(converter(), eventRepository(), christmasEventStrategy());
+    }
+
+    private Converter converter() {
+        return new Converter();
+    }
+
+    private EventRepository eventRepository() {
+        return EventRepository.getEventRepository();
+    }
+
+    private EventStrategy christmasEventStrategy() {
+        return new ChristmasEventStrategy(christmasTotalDiscount());
+    }
+
+    private ChristmasTotalDiscount christmasTotalDiscount() {
+        return new ChristmasTotalDiscount();
+    }
+
+    public void endEvent() {
+        Console.close();
+    }
+}

리뷰: 객체 별 필요 객체를 주입하고 있는 클래스로 보이는데 어떤 클래스가 어디에 주입되는지 쉽게 볼 수 있는 좋은 객체 구현 방법이라고 생각되네요.. 멋집니다!
@@ -0,0 +1,47 @@
+package christmas.model.domain.date;
+
+import static christmas.model.util.ErrorMessage.INVALID_DATE_MESSAGE;
+
+import christmas.model.util.MyIllegalArgumentException;
+
+public class Date {
+    private static final int CHRISTMAS_DATE = 25;
+    private static final int WEEK = 7;
+    private static final int FIRST_FRIDAY = 1;
+    private static final int FIRST_SATURDAY = 2;
+    private static final int FIRST_DAY = 1;
+    private static final int LAST_DAY = 31;
+
+    private final int date;
+
+    public Date(int date) {
+        validateDate(date);
+        this.date = date;
+    }
+
+    private void validateDate(int date) {
+        if (FIRST_DAY > date || date > LAST_DAY) {
+            throw new MyIllegalArgumentException(INVALID_DATE_MESSAGE);
+        }
+    }
+
+    public boolean isSpecialDate() {
+        return SpecialDate.checkSpecialDate(this);
+    }
+
+    boolean isSameDate(int date) {
+        return this.date == date;
+    }
+
+    public boolean isWeekEnd() {
+        return date % WEEK == FIRST_FRIDAY || date % WEEK == FIRST_SATURDAY;
+    }
+
+    public int getDDayFromChristmas() {
+        return CHRISTMAS_DATE - date;
+    }
+
+    public boolean isAfterChristmas() {
+        return date > CHRISTMAS_DATE;
+    }
+}

리뷰: 계속 코드리뷰를 받으면서 객체에서 검증한 값을 전달한다 라는 의미가 잘 와닿지 않았는데 해당 방식처럼 구현했어야 했네요. 계속 들었던 내용이었는데 코드도 깔끔하고 확 와닿네요. 정말 잘 배우게 됬네요! 제가 오히려 코드리뷰 안했으면 큰일날뻔 했습니다 ㅋㅋ..
@@ -0,0 +1,16 @@
+package christmas.model.domain.dto;
+
+import christmas.model.domain.date.Date;
+
+public class DateDto extends Date {
+    private final int date;
+
+    public DateDto(int date) {
+        super(date);
+        this.date = date;
+    }
+
+    public int getDate() {
+        return date;
+    }
+}

리뷰: 저는 DTO 에서 값을 가져오는 걸 어떻게 써야하지 정말 고민했는데 이렇게 써야 했네요.. 계속 배우게 되는 것  같습니다
@@ -0,0 +1,32 @@
+package christmas.model.domain.dto;
+
+import christmas.model.domain.event.benefit.Badge;
+import christmas.model.domain.event.benefit.EventBenefits;
+
+public class EventResultsDto {
+    private final EventBenefits eventBenefits;
+    private final Badge badge;
+
+    public EventResultsDto(EventBenefits eventBenefits, Badge badge) {
+        this.eventBenefits = eventBenefits;
+        this.badge = badge;
+    }
+
+    public int getTotalDiscount() {
+        return eventBenefits.getDiscountBenefit();
+    }
+
+    public int getTotalBenefitAmount() {
+        return eventBenefits.getTotalBenefit();
+    }
+
+    public Badge getBadge() {
+        return badge;
+    }
+
+    public String getGiftMessage() { return eventBenefits.getGiftMessage();}
+
+    public String getBenefitMessage() {
+        return eventBenefits.makeBenefitMessage();
+    }
+}

리뷰: 저는 DTO 가 계속 도메인의 값을 가지고 있으면 안된다 생각해서 String 이나 int 처럼 기본 타입으로 가지고 있고 다시 Service로 보낼때 해당 값을 사용하기 참 어려웠는데 이 정도로 생성된 객체를 가지고 있는 방식은 괜찮다고 생각하시나요..? 의견이 궁금합니다!
@@ -0,0 +1,36 @@
+package christmas.model.domain.event;
+
+import christmas.model.domain.event.benefit.Badge;
+import java.util.HashMap;
+import java.util.Map;
+
+public class EventRepository {
+    private static EventRepository instance;
+    private final Map<Long, EventResults> eventRepository;
+    private final Map<Long, Badge> badgeRepository;
+    private long id;
+
+    private EventRepository(Map<Long, EventResults> eventRepository, Map<Long, Badge> badgeRepository) {
+        this.eventRepository = eventRepository;
+        this.badgeRepository = badgeRepository;
+        id = 0L;
+    }
+
+    public static EventRepository getEventRepository() {
+        if (instance == null) {
+            instance = new EventRepository(new HashMap<>(), new HashMap<>());
+        }
+        return instance;
+    }
+
+    public long save(EventResults eventResults) {
+        long currentId = id++;
+        eventRepository.put(currentId, eventResults);
+        badgeRepository.put(currentId, eventResults.getBadge());
+        return id;
+    }
+
+    public EventResults getEventResultsById(long id) {
+        return eventRepository.get(id);
+    }
+}

리뷰: 한번의 로직만 실행하다보니 Repository 구현은 생각도 안했는데 생각보다 많이 깔끔하고 좋을 것 같네요.. 좋은 코드입니다
@@ -0,0 +1,29 @@
+package christmas.model.domain.event.benefit;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+public enum Badge {
+    NONE(0, "없음"), STAR(5000, "별"),
+    TREE(10000, "트리"), SANTA(20000, "산타");
+
+    private final int minimumBenefitAmount;
+    private final String name;
+
+    Badge(int minimumBenefitAmount, String name) {
+        this.minimumBenefitAmount = minimumBenefitAmount;
+        this.name = name;
+    }
+
+    public static Badge getBadge(int totalBenefitAmount) {
+        return Arrays.stream(values())
+                .filter(badge -> badge.minimumBenefitAmount <= totalBenefitAmount)
+                .max(Comparator.comparingInt(b -> b.minimumBenefitAmount))
+                .orElse(NONE);
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+}

리뷰: getName으로 구현하는  것과 toString을 오버라이딩 해서 사용하는 방법이 다르다고 생각하시는 부분이 있을까요??
@@ -0,0 +1,27 @@
+package christmas.model.domain.event.benefit;
+
+import java.util.stream.Stream;
+
+public class DiscountBenefit {
+
+    private final int discountBenefit;
+    private final Stream<String> discountMessageStream;
+
+    public DiscountBenefit(int discountBenefit, Stream<String> discountMessageStream) {
+        this.discountBenefit = discountBenefit;
+        this.discountMessageStream = discountMessageStream;
+    }
+
+    public static DiscountBenefit makeZeroDiscountBenefit() {
+        return new DiscountBenefit(0, Stream.empty());
+    }
+
+    public int getDiscountBenefit() {
+        return discountBenefit;
+    }
+
+    public Stream<String> getDiscountMessage() {
+        return discountMessageStream;
+    }
+}
+

리뷰: 해당 객체 생성을 static으로 사용하신 이유가 있나요?? 저는 클래스 오버로딩으로 사용해서 어떻게 생각하시는지 궁금합니다
@@ -0,0 +1,50 @@
+package christmas.model.domain.event.benefit.gift;
+
+import christmas.model.domain.order.TotalOrderPrice;
+import java.util.stream.Stream;
+
+public class ChristmasGift implements Gift {
+    private static final int MINIMUM_CHRISTMAS_GIFT_PRICE = 120000;
+    private static final int CHRISTMAS_GIFT_PRICE = -25000;
+    private static final String CHRISTMAS_GIFT_TRUE_MESSAGE = "샴페인 1개";
+    private static final String CHRISTMAS_GIFT_FALSE_MESSAGE = "없음";
+    private static final String CHRISTMAS_GIFT_BENEFIT_MESSAGE = "증정 이벤트: %,d원";
+
+    private final boolean giftAvailability;
+
+    private ChristmasGift(int totalPrice) {
+        this.giftAvailability = totalPrice >= MINIMUM_CHRISTMAS_GIFT_PRICE;
+    }
+
+    public static Gift makeNoneGift() {
+        return new ChristmasGift(0);
+    }
+
+    public static Gift makeChristmasGift(TotalOrderPrice totalOrderPrice) {
+        return new ChristmasGift(totalOrderPrice.getTotalOrderPrice());
+    }
+
+    @Override
+    public int getGiftBenefit() {
+        if (giftAvailability) {
+            return CHRISTMAS_GIFT_PRICE;
+        }
+        return 0;
+    }
+
+    @Override
+    public String makeGiftMessage() {
+        if (giftAvailability) {
+            return CHRISTMAS_GIFT_TRUE_MESSAGE;
+        }
+        return CHRISTMAS_GIFT_FALSE_MESSAGE;
+    }
+
+    @Override
+    public Stream<String> makeGiftBenefitMessageStream() {
+        if (giftAvailability) {
+            return Stream.of(String.format(CHRISTMAS_GIFT_BENEFIT_MESSAGE, CHRISTMAS_GIFT_PRICE));
+        }
+        return Stream.empty();
+    }
+}

리뷰: Gift 인터페이스로 분리해서 상속받은 방법이 좋습니다! 저도 다른 분들의 의견을 보고 이렇게 변경한 코드를 작성해보려는데 다시 봐도 좋은 코드인 것 같습니다
@@ -0,0 +1,25 @@
+package christmas.model.domain.event.discount;
+
+import christmas.model.domain.event.discount.christmas.ChristmasDDayDiscount;
+import christmas.model.domain.event.discount.christmas.ChristmasSpecialDiscount;
+import christmas.model.domain.event.discount.christmas.ChristmasWeekDiscount;
+import christmas.model.domain.event.discount.christmas.ChristmasWeekendDiscount;
+import java.util.Arrays;
+import java.util.List;
+
+public enum ChristmasDiscountStrategies {
+    DDAY(new ChristmasDDayDiscount()), WEEK(new ChristmasWeekDiscount()), WEEKEND(new ChristmasWeekendDiscount()), SPECIAL(new ChristmasSpecialDiscount());
+
+    private final Discount discount;
+
+    ChristmasDiscountStrategies(Discount discount) {
+        this.discount = discount;
+    }
+
+    public static List<Discount> getAllDiscountStrategy() {
+        return Arrays.stream(values())
+                .map(discountStrategy -> discountStrategy.discount)
+                .peek(Discount::reset)
+                .toList();
+    }
+}

리뷰: 할인 인터페이스를 상속받은 객체들의 값을 초기화하여 생성하는 방법이 경이롭네요. 변경할 코드에 적용할 내용이 하나 더 늘은 것 같습니다!
@@ -0,0 +1,30 @@
+package christmas.model.domain.event.discount;
+
+import christmas.model.domain.date.Date;
+import christmas.model.domain.order.RequestOrders;
+
+public abstract class Discount {
+    private int discountAmount;
+
+    public abstract void calculateDiscountAmount(RequestOrders requestOrders, Date date);
+
+    public abstract String toString();
+
+    protected abstract void reset();
+
+    public boolean isNotZero() {
+        return discountAmount != 0;
+    }
+
+    public void resetDiscount() {
+        discountAmount = 0;
+    }
+
+    public int getDiscountAmount() {
+        return discountAmount;
+    }
+
+    protected void updateDiscountAmount(int discountAmount) {
+        this.discountAmount -= discountAmount;
+    }
+}

리뷰: 할인 객체를 상속받는 객체에 reset 하는 행동을 강제하기 위해 reset() 메서드를 resetDiscount() 말고 따로 만드신 건가요??
@@ -0,0 +1,59 @@
+package christmas.model.domain.menu;
+
+import static christmas.model.domain.menu.MenuCategory.DESSERT;
+import static christmas.model.domain.menu.MenuCategory.MAIN;
+import static christmas.model.domain.menu.MenuCategory.APPETIZER;
+import static christmas.model.domain.menu.MenuCategory.BEVERAGE;
+import static christmas.model.util.ErrorMessage.INVALID_ORDER_MESSAGE;
+
+import christmas.model.util.MyIllegalArgumentException;
+import java.util.Arrays;
+import java.util.Objects;
+
+public enum Menu {
+    MUSHROOM_SOUP("양송이수프", 6000, APPETIZER),
+    TAPAS("타파스", 5500, APPETIZER),
+    CAESAR_SALAD("시저샐러드", 8000, APPETIZER),
+    T_BONE_STAKE("티본스테이크", 55000, MAIN),
+    BARBECUE_RIB("바비큐립", 54000, MAIN),
+    SEE_FOOD_PASTA("해산물파스타", 35000, MAIN),
+    CHRISTMAS_PASTA("크리스마스파스타", 25000, MAIN),
+    CHOCO_CAKE("초코케이크", 15000, DESSERT),
+    ICE_CREAM("아이스크림", 5000, DESSERT),
+    ZERO_COKE("제로콜라", 3000, BEVERAGE),
+    RED_WINE("레드와인", 60000, BEVERAGE),
+    CHAMPAGNE("샴페인", 25000, BEVERAGE);
+
+    private final String name;
+    private final int price;
+    private final MenuCategory category;
+
+    Menu(String name, int price, MenuCategory category) {
+        this.name = name;
+        this.price = price;
+        this.category = category;
+    }
+
+    public static Menu getMenuByName(String menuName) {
+        return Arrays.stream(values())
+                .filter(menu -> menu.checkName(menuName))
+                .findAny()
+                .orElseThrow(() -> new MyIllegalArgumentException(INVALID_ORDER_MESSAGE));
+    }
+
+    public boolean checkName(String name) {
+        return Objects.equals(this.name, name);
+    }
+
+    public boolean checkCategory(MenuCategory category) {
+        return this.category.checkCategory(category);
+    }
+
+    public int getPrice() {
+        return price;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

리뷰: `HashMap으로 캐싱` 해두는 방법도 추천드립니다.

Enum에선 static 블록을 추가할 수 있는데요, 요런식으로 구현할 수 있습니다!
```java
public enum Menu {
    // 열거형 상수들 ...

    private static final Map<String, Menu> cachedMenu;

    static {
        for (Menu menu : values()) {
            cachedMenu.put(menu.getName(), menu);
        }
    }
}
```
이렇게 Map으로 캐싱해두면, 매 번 열거형 상수들을 순회하지 않아도 되는 장점이 있습니다 :)
하지만 NPE가 발생할 위험이 있기 때문에, Optional을 사용까지 고려해보시면 좋을 것 같습니다. !!
@@ -0,0 +1,41 @@
+package christmas.model.domain.event.strategy;
+
+import christmas.model.domain.date.Date;
+import christmas.model.domain.event.benefit.Badge;
+import christmas.model.domain.event.benefit.DiscountBenefit;
+import christmas.model.domain.event.benefit.gift.ChristmasGift;
+import christmas.model.domain.event.benefit.EventBenefits;
+import christmas.model.domain.event.EventResults;
+import christmas.model.domain.event.benefit.gift.Gift;
+import christmas.model.domain.event.benefit.totaldiscount.ChristmasTotalDiscount;
+import christmas.model.domain.event.benefit.totaldiscount.TotalDiscount;
+import christmas.model.domain.order.RequestOrders;
+import christmas.model.domain.order.TotalOrderPrice;
+
+public class ChristmasEventStrategy implements EventStrategy {
+    private final int CHRISTMAS_EVENT_APPLY_MINIMUM_PRICE = 10000;
+
+    private final TotalDiscount christmasTotalDiscount;
+
+    public ChristmasEventStrategy(ChristmasTotalDiscount christmasTotalDiscount) {
+        this.christmasTotalDiscount = christmasTotalDiscount;
+    }
+
+    @Override
+    public EventResults updateEventResult(RequestOrders requestOrders, Date date) {
+        christmasTotalDiscount.applyDiscount(requestOrders, date);
+        DiscountBenefit discountBenefit = christmasTotalDiscount.makeDiscountBenefit();
+        TotalOrderPrice totalOrderPrice = requestOrders.getTotalOrderPrice();
+        Gift gift = ChristmasGift.makeChristmasGift(totalOrderPrice);
+
+        EventBenefits eventBenefits = new EventBenefits(discountBenefit, gift);
+        Badge badge = Badge.getBadge(Math.abs(eventBenefits.getTotalBenefit()));
+        return new EventResults(eventBenefits, badge);
+    }
+
+    @Override
+    public int getMinimumOrderPrice() {
+        return CHRISTMAS_EVENT_APPLY_MINIMUM_PRICE;
+    }
+
+}
\ No newline at end of file

리뷰: 할인 이벤트, 증정 이벤트를 묶어서 관리하니 굉장히 응집도도 놓고 좋은 것 같습니다..!! 👍
@@ -0,0 +1,30 @@
+package christmas.model.domain.event.discount;
+
+import christmas.model.domain.date.Date;
+import christmas.model.domain.order.RequestOrders;
+
+public abstract class Discount {
+    private int discountAmount;
+
+    public abstract void calculateDiscountAmount(RequestOrders requestOrders, Date date);
+
+    public abstract String toString();
+
+    protected abstract void reset();
+
+    public boolean isNotZero() {
+        return discountAmount != 0;
+    }
+
+    public void resetDiscount() {
+        discountAmount = 0;
+    }
+
+    public int getDiscountAmount() {
+        return discountAmount;
+    }
+
+    protected void updateDiscountAmount(int discountAmount) {
+        this.discountAmount -= discountAmount;
+    }
+}

리뷰: Discount을 추상클래스로 두고 상속 관계를 활용하신 이유를 여쭙고 싶어요!
@@ -0,0 +1,11 @@
+package christmas.model.domain.event.strategy;
+
+import christmas.model.domain.date.Date;
+import christmas.model.domain.event.EventResults;
+import christmas.model.domain.order.RequestOrders;
+
+public interface EventStrategy {
+    EventResults updateEventResult(RequestOrders requestOrders, Date date);
+
+    int getMinimumOrderPrice();
+}

리뷰: EventStrategy 를 인터페이스로 추상화하신 이유가 궁금합니다!