ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,90 @@
+import { API_PATH } from '@apis/constants';
+import { useQuery } from '@apis/common/useQuery';
+import { ReviewCycle } from './entities';
+import { ReviewCycleRequest, ReviewCycleResponse } from './type';
+import { useDelete, usePost, usePut } from '@apis/common/useMutation';
+import { get } from 'lodash';
+
+export function useGetReviewCycles() {
+  const {
+    data,
+    isLoading,
+    mutate: refetchReviewCycle,
+  } = useQuery<{ reviewCycles: ReviewCycle[] }>(API_PATH.REVIEW_CYCLES);
+
+  return {
+    reviewCycles: data?.reviewCycles,
+    isLoading,
+    refetchReviewCycle,
+  };
+}
+
+export function useCreateReviewCycle({
+  onSuccess,
+  onError,
+}: {
+  onSuccess: () => void;
+  onError: (message: string) => void;
+}) {
+  const { trigger, isMutating } = usePost<ReviewCycleRequest, ReviewCycleResponse>({
+    endpoint: API_PATH.REVIEW_CYCLES,
+    onSuccess: onSuccess,
+    onError: err => {
+      const errorMessage = get(err, 'response.data.error.message', '리뷰 사이클 생성에 실패했습니다.');
+      onError(errorMessage);
+    },
+  });
+
+  return {
+    createReviewCycle: (reviewCycle: ReviewCycleRequest) => trigger(reviewCycle),
+    isMutating,
+  };
+}
+
+export function useUpdateReviewCycle({
+  entityId,
+  onSuccess,
+  onError,
+}: {
+  entityId?: number;
+  onSuccess: () => void;
+  onError: (message: string) => void;
+}) {
+  const { trigger, isMutating } = usePut<ReviewCycleRequest, ReviewCycleResponse>({
+    endpoint: entityId ? `${API_PATH.REVIEW_CYCLES}/${entityId}` : null,
+    onSuccess: onSuccess,
+    onError: err => {
+      const errorMessage = get(err, 'response.data.error.message', '리뷰 사이클 수정에 실패했습니다.');
+      onError(errorMessage);
+    },
+  });
+
+  return {
+    updateReviewCycle: (reviewCycle: ReviewCycleRequest) => trigger(reviewCycle),
+    isMutating,
+  };
+}
+
+export function useDeleteReviewCycle({
+  entityId,
+  onSuccess,
+  onError,
+}: {
+  entityId: number;
+  onSuccess: () => void;
+  onError: (message: string) => void;
+}) {
+  const { trigger, isMutating } = useDelete({
+    endpoint: `${API_PATH.REVIEW_CYCLES}/${entityId}`,
+    onSuccess,
+    onError: err => {
+      const errorMessage = get(err, 'response.data.error.message', '리뷰 사이클 삭제에 실패했습니다.');
+      onError(errorMessage);
+    },
+  });
+
+  return {
+    deleteReviewCycle: trigger,
+    isMutating,
+  };
+}

리뷰: 이 부분은 SWR 기반의 인터페이스(trigger)를 감추고 인터페이스를 통일하기 위한 목적이었어요. 추후에 SWR을 tanstack-query 등 다른 라이브러리로 변경하더라도 동일하게 createReviewCycle 인터페이스를 사용하도록 해서 사용처에서는 코드를 수정할 필요가 없도록 하고자 했습니다.
@@ -0,0 +1,17 @@
+import { Rule } from 'antd/lib/form';
+
+export const REVIEW_CYCLE_FORM_NAMES = {
+  name: 'name',
+  reviewees: 'reviewees',
+  question: {
+    title: 'title',
+    description: 'description',
+  },
+} as const;
+
+export const REVIEW_CYCLE_RULES: Record<string, Rule[]> = {
+  reviewCycleName: [{ required: true, message: '리뷰 정책 이름을 입력하세요.' }],
+  reviewees: [{ required: true, message: '리뷰 받는 사람을 선택하세요.' }],
+  questionTitle: [{ required: true, message: '질문 제목을 입력하세요.' }],
+  questionDescription: [{ required: true, message: '질문 설명을 입력하세요.' }],
+};

리뷰: 해당 form의 요소가 어떻게 구성되어 있는지 한눈에 파악하기 쉽지 않을까 했었는데, 하나의 객체로 묶는게 나았으려나 싶군요!
@@ -0,0 +1,25 @@
+import { DeleteOutlined } from '@ant-design/icons';
+import { useDeleteReviewCycle, useGetReviewCycles } from '@apis/review/useReviewCycles';
+import { message } from 'antd';
+
+function DeleteButton({ entityId }: { entityId: number }) {
+  const { refetchReviewCycle } = useGetReviewCycles();
+  const { deleteReviewCycle } = useDeleteReviewCycle({
+    entityId,
+    onSuccess: refetchReviewCycle,
+    onError: message.error,
+  });
+
+  return (
+    <button
+      onClick={e => {
+        e.stopPropagation();
+        deleteReviewCycle();
+      }}
+    >
+      <DeleteOutlined />
+    </button>
+  );
+}
+
+export default DeleteButton;

리뷰: 현재 테이블 onRowClick 시 리뷰 사이클 수정 모달이 열리도록 이벤트를 등록해두었는데요, 삭제 버튼 시에는 이벤트 전파를 막도록 했습니다.
@@ -0,0 +1,25 @@
+import { DeleteOutlined } from '@ant-design/icons';
+import { useDeleteReviewCycle, useGetReviewCycles } from '@apis/review/useReviewCycles';
+import { message } from 'antd';
+
+function DeleteButton({ entityId }: { entityId: number }) {
+  const { refetchReviewCycle } = useGetReviewCycles();
+  const { deleteReviewCycle } = useDeleteReviewCycle({
+    entityId,
+    onSuccess: refetchReviewCycle,
+    onError: message.error,
+  });
+
+  return (
+    <button
+      onClick={e => {
+        e.stopPropagation();
+        deleteReviewCycle();
+      }}
+    >
+      <DeleteOutlined />
+    </button>
+  );
+}
+
+export default DeleteButton;

리뷰: 넵 button으로 감싸는게 좋겠군요.

> 반영했습니다! [refactor: 삭제 버튼 구조 변경](https://github.com/lemonbase-labs/journee-on-boarding-project/pull/7/commits/2201091e80c11c71bb97c3cf131410067cb04478)
@@ -0,0 +1,67 @@
+import { Button, Form, Input, Select, Space } from 'antd';
+import { REVIEW_CYCLE_FORM_NAMES, REVIEW_CYCLE_RULES } from './constants';
+import useMembers from '@apis/review/useMembers';
+import { ReviewCycleRequest } from '@apis/review/type';
+import { ReviewCycle } from '@apis/review/entities';
+
+interface Props {
+  onFinish: (values: ReviewCycleRequest) => void;
+  onReset: () => void;
+  initialValues?: {
+    name: ReviewCycle['name'];
+    reviewees: number[];
+    title: ReviewCycle['question']['title'];
+    description: ReviewCycle['question']['description'];
+  };
+  confirmButtonProps?: { text: string };
+  deleteButtonProps?: { text: string; handleClick: () => void };
+}
+
+export default function ReviewCycleForm({
+  onFinish,
+  onReset,
+  initialValues,
+  confirmButtonProps = { text: '확인' },
+  deleteButtonProps,
+}: Props) {
+  const { members, isLoading } = useMembers();
+
+  return (
+    <Form labelAlign="right" onFinish={onFinish} onReset={onReset} initialValues={initialValues}>
+      <Form.Item label="리뷰 정책 이름" name={REVIEW_CYCLE_FORM_NAMES.name} rules={REVIEW_CYCLE_RULES.reviewCycleName}>
+        <Input />
+      </Form.Item>
+
+      <Form.Item label="리뷰 받는 사람" name={REVIEW_CYCLE_FORM_NAMES.reviewees} rules={REVIEW_CYCLE_RULES.reviewees}>
+        <Select
+          mode="multiple"
+          loading={isLoading}
+          placeholder="리뷰 받는 사람을 선택해주세요."
+          optionFilterProp="label"
+          options={members?.map(m => ({ key: m.entityId, label: m.name, value: m.entityId }))}
+        />
+      </Form.Item>
+
+      <Form.Item label="질문" name={REVIEW_CYCLE_FORM_NAMES.question.title} rules={REVIEW_CYCLE_RULES.questionTitle}>
+        <Input />
+      </Form.Item>
+
+      <Form.Item
+        label="질문 설명"
+        name={REVIEW_CYCLE_FORM_NAMES.question.description}
+        rules={REVIEW_CYCLE_RULES.questionDescription}
+      >
+        <Input />
+      </Form.Item>
+      <Form.Item style={{ textAlign: 'right' }}>
+        <Space size={10}>
+          <Button htmlType="reset">취소</Button>
+          {deleteButtonProps && <Button onClick={deleteButtonProps.handleClick}>{deleteButtonProps.text}</Button>}
+          <Button type="primary" htmlType="submit">
+            {confirmButtonProps.text}
+          </Button>
+        </Space>
+      </Form.Item>
+    </Form>
+  );
+}

리뷰: 아하.. 저는 직관적이라고 생각했는데, 유지보수 차원에서 어떤 어려움인지 혹은 어떻게 분리되어야 하는지 좀 더 설명해주실 수 있을까요?

실제로 현재 레몬베이스 모달 훅인 useBasicModal도 비슷한 패턴으로 사용하고 있는데, 저는 유지보수 차원에서 어려움이 있어서 개선해야 한다고 생각하진 않았어서요!
@@ -0,0 +1,43 @@
+import { Button } from 'antd';
+import { RoundContent } from 'components/common/RoundContent';
+import useReviewCycleCreateModal from './ReviewCycleModals/useReviewCycleCreateModal';
+import ReviewCycleTable from './ReviewCycleTable';
+import useReviewCycleUpdateModal from './ReviewCycleModals/useReviewCycleUpdateModal';
+import useSelectedReviewCycle from './useSelectedReviewCycle';
+
+function ReviewCycles() {
+  const { selectedReviewCycle } = useSelectedReviewCycle();
+
+  const reviewCycleCreateModal = useReviewCycleCreateModal();
+  const reviewCycleUpdateModal = useReviewCycleUpdateModal({
+    entityId: selectedReviewCycle?.entityId,
+  });
+
+  function openReviewCycleCreateModal() {
+    reviewCycleCreateModal.open();
+  }
+
+  function openReviewCycleUpdateModal() {
+    reviewCycleUpdateModal.open();
+  }
+
+  return (
+    <RoundContent>
+      <RoundContent.Header
+        title="리뷰 정책 목록"
+        rightArea={
+          <Button type="primary" size="large" onClick={openReviewCycleCreateModal}>
+            리뷰 정책 생성
+          </Button>
+        }
+      />
+      <RoundContent.Body>
+        <ReviewCycleTable onRowClick={openReviewCycleUpdateModal} />
+      </RoundContent.Body>
+      {reviewCycleCreateModal.render()}
+      {reviewCycleUpdateModal.render()}
+    </RoundContent>
+  );
+}
+
+export default ReviewCycles;

리뷰: 네!! 반영했습니다!

[refactor: 인스턴스 camelcase로 수정](https://github.com/lemonbase-labs/journee-on-boarding-project/pull/7/commits/3a4fecb79aa6c1a53951900d79c308af0a87d940)
@@ -0,0 +1,90 @@
+import { API_PATH } from '@apis/constants';
+import { useQuery } from '@apis/common/useQuery';
+import { ReviewCycle } from './entities';
+import { ReviewCycleRequest, ReviewCycleResponse } from './type';
+import { useDelete, usePost, usePut } from '@apis/common/useMutation';
+import { get } from 'lodash';
+
+export function useGetReviewCycles() {
+  const {
+    data,
+    isLoading,
+    mutate: refetchReviewCycle,
+  } = useQuery<{ reviewCycles: ReviewCycle[] }>(API_PATH.REVIEW_CYCLES);
+
+  return {
+    reviewCycles: data?.reviewCycles,
+    isLoading,
+    refetchReviewCycle,
+  };
+}
+
+export function useCreateReviewCycle({
+  onSuccess,
+  onError,
+}: {
+  onSuccess: () => void;
+  onError: (message: string) => void;
+}) {
+  const { trigger, isMutating } = usePost<ReviewCycleRequest, ReviewCycleResponse>({
+    endpoint: API_PATH.REVIEW_CYCLES,
+    onSuccess: onSuccess,
+    onError: err => {
+      const errorMessage = get(err, 'response.data.error.message', '리뷰 사이클 생성에 실패했습니다.');
+      onError(errorMessage);
+    },
+  });
+
+  return {
+    createReviewCycle: (reviewCycle: ReviewCycleRequest) => trigger(reviewCycle),
+    isMutating,
+  };
+}
+
+export function useUpdateReviewCycle({
+  entityId,
+  onSuccess,
+  onError,
+}: {
+  entityId?: number;
+  onSuccess: () => void;
+  onError: (message: string) => void;
+}) {
+  const { trigger, isMutating } = usePut<ReviewCycleRequest, ReviewCycleResponse>({
+    endpoint: entityId ? `${API_PATH.REVIEW_CYCLES}/${entityId}` : null,
+    onSuccess: onSuccess,
+    onError: err => {
+      const errorMessage = get(err, 'response.data.error.message', '리뷰 사이클 수정에 실패했습니다.');
+      onError(errorMessage);
+    },
+  });
+
+  return {
+    updateReviewCycle: (reviewCycle: ReviewCycleRequest) => trigger(reviewCycle),
+    isMutating,
+  };
+}
+
+export function useDeleteReviewCycle({
+  entityId,
+  onSuccess,
+  onError,
+}: {
+  entityId: number;
+  onSuccess: () => void;
+  onError: (message: string) => void;
+}) {
+  const { trigger, isMutating } = useDelete({
+    endpoint: `${API_PATH.REVIEW_CYCLES}/${entityId}`,
+    onSuccess,
+    onError: err => {
+      const errorMessage = get(err, 'response.data.error.message', '리뷰 사이클 삭제에 실패했습니다.');
+      onError(errorMessage);
+    },
+  });
+
+  return {
+    deleteReviewCycle: trigger,
+    isMutating,
+  };
+}

리뷰: 오호 인터페이스 통일 목적! 이해했습니다~
고렇다면 같은 파일 [L87](https://github.com/lemonbase-labs/journee-on-boarding-project/pull/7/files#diff-3ab91e36eb619267b0f18915f2e642a8452aece2c4e8220b0bce854dc2e8d68eR87)에 `deleteReviewCycle: trigger`는 요 처리가 누락된 것 같아 같이 반영하면 좋을 것 같아요~
@@ -1,16 +1,35 @@
 package nextstep.subway.domain;
 
-import javax.persistence.*;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
 
 @Entity
 public class Station extends BaseEntity {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
+
     @Column(unique = true)
     private String name;
 
-    public Station() {
+    @ManyToOne
+    @JoinColumn(name = "line_id")
+    private Line line;
+
+    private Integer distance;
+
+    protected Station() {
+    }
+
+    Station(Long id, String name) {
+        this.id = id;
+        this.name = name;
     }
 
     public Station(String name) {
@@ -24,4 +43,61 @@ public Long getId() {
     public String getName() {
         return name;
     }
+
+    public Integer getDistance() {
+        return distance;
+    }
+
+    public Line getLine() {
+        return line;
+    }
+
+    public void setLine(Line line) {
+        if (Objects.nonNull(this.line)) {
+            this.line.getStations().removeStation(this);
+        }
+        this.line = line;
+        if (!line.getStations().containsStation(this)) {
+            line.getStations().addStation(this);
+        }
+    }
+
+    public void clearLine() {
+        this.line = null;
+    }
+
+    public void clearRelatedStation() {
+        if (line != null) {
+            this.line.getStations().removeStation(this);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        Station station = (Station) o;
+
+        return id.equals(station.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "Station{" +
+                "id=" + id +
+                ", name='" + name + '\'' +
+                ", line=" + line +
+                ", distance=" + distance +
+                '}';
+    }
 }

리뷰: 역이 Line을 가지는게 맞을까요?
하나의 역이 여러 노선에 속하면 어떻게 되나요.
예를 들어 강남역은 2호선과 신분당선 두가지가 있어요.
@@ -0,0 +1,86 @@
+package nextstep.subway.application;
+
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import nextstep.subway.domain.Line;
+import nextstep.subway.domain.LineRepository;
+import nextstep.subway.domain.Station;
+import nextstep.subway.domain.StationRepository;
+import nextstep.subway.dto.LineCreateRequest;
+import nextstep.subway.dto.LineResponse;
+import nextstep.subway.dto.LineUpdateRequest;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@Transactional(readOnly = true)
+public class LineService {
+    private final LineRepository lineRepository;
+
+    private final StationRepository stationRepository;
+
+    public LineService(LineRepository lineRepository, StationRepository stationRepository) {
+        this.lineRepository = lineRepository;
+        this.stationRepository = stationRepository;
+    }
+
+    @Transactional
+    public LineResponse saveLine(LineCreateRequest lineCreateRequest) {
+        Line persistLine = lineRepository.save(lineCreateRequest.toLine());
+        addStations(persistLine, lineCreateRequest);
+        return LineResponse.of(persistLine);
+    }
+
+    public LineResponse findLine(Long id) {
+        Optional<Line> line = lineRepository.findById(id);
+        return LineResponse.of(line.get());
+    }
+
+    public List<LineResponse> findAllLines() {
+        return lineRepository.findAll().stream()
+                .map(LineResponse::of)
+                .collect(Collectors.toList());
+    }
+
+    @Transactional
+    public LineResponse updateLine(Long id, LineUpdateRequest lineUpdateRequest) {
+        Line line = lineRepository.findById(id).get();
+        line.updateLine(lineUpdateRequest);
+        return LineResponse.of(line);
+    }
+
+    @Transactional
+    public void deleteLineById(Long id) {
+        Optional<Line> line = lineRepository.findById(id);
+        if (line.isPresent()) {
+            line.get().clearRelatedLines();
+            lineRepository.delete(line.get());
+        }
+    }
+
+    private void addStations(Line persistLine, LineCreateRequest lineCreateRequest) {
+        addUpLine(persistLine, lineCreateRequest);
+        addDownLine(persistLine, lineCreateRequest);
+    }
+
+    private void addUpLine(Line line, LineCreateRequest lineCreateRequest) {
+        if (lineCreateRequest.hasUpStationsId()) {
+            Station station = getStation(lineCreateRequest.getUpStationId());
+            line.addStation(station);
+        }
+    }
+
+    private void addDownLine(Line line, LineCreateRequest lineCreateRequest) {
+        if (lineCreateRequest.hasDownStationsId()) {
+            Station station = getStation(lineCreateRequest.getDownStationId());
+            line.addStation(station);
+        }
+    }
+
+    private Station getStation(Long stationId) {
+        return stationRepository.findById(stationId)
+                .orElseThrow(() -> new NoSuchElementException("id(" + stationId + ")에 해당하는 역이 없습니다."));
+    }
+}

리뷰: 리팩토링 단계에서 미처 수정하지 못한 것 같습니다...
@@ -0,0 +1,63 @@
+package christmas.controller;
+
+import christmas.domain.PromotionService;
+import christmas.domain.order.Day;
+import christmas.domain.order.Order;
+import christmas.dto.response.DiscountPreviewResponse;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+
+import java.util.function.Supplier;
+
+public class PromotionController {
+
+    private PromotionController() {
+
+    }
+
+    public static PromotionController create() {
+        return new PromotionController();
+    }
+
+
+    public void run() {
+        printIntroductionMessage();
+
+        Day day = readWithExceptionHandling(PromotionController::readDay);
+        Order order = readWithExceptionHandling(PromotionController::readOrder);
+        PromotionService promotionService = PromotionService.create(order, day);
+        closeRead();
+
+        printDiscountPreviewMessage(promotionService);
+    }
+
+    private static void printIntroductionMessage() {
+        OutputView.printIntroductionMessage();
+    }
+
+    private static <T> T readWithExceptionHandling(Supplier<T> reader) {
+        while (true) {
+            try {
+                return reader.get();
+            } catch (IllegalArgumentException exception) {
+                System.out.println(exception.getMessage());
+            }
+        }
+    }
+
+    private static Day readDay() throws IllegalArgumentException {
+        return Day.create(InputView.readEstimatedVisitingDate());
+    }
+
+    private static Order readOrder() throws IllegalArgumentException {
+        return Order.create(InputView.readOrder());
+    }
+
+    private static void closeRead() {
+        InputView.closeRead();
+    }
+
+    private static void printDiscountPreviewMessage(PromotionService promotionService) {
+        OutputView.printDiscountPreviewMessage(DiscountPreviewResponse.from(promotionService));
+    }
+}

리뷰: static 메소드가 속도가 빨라진다는 장점이 있지만 객체지향에서 멀어지기 때문에 지양해야한다고 생각합니다!

혹시 Controller 내 메소드들을 모두 static 메소드로 선언하신 이유가 있으실까요??
@@ -0,0 +1,40 @@
+package christmas.domain.order;
+
+import christmas.validator.OrderValidator;
+
+import java.util.Objects;
+
+public class Day {
+
+    private final Integer day;
+
+    private Day(Integer day) {
+        validate(day);
+        this.day = day;
+    }
+
+    public static Day create(Integer day) {
+        return new Day(day);
+    }
+
+    private void validate(Integer day) {
+        OrderValidator.validateIsDateInRange(day);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Day day1 = (Day) o;
+        return Objects.equals(day, day1.day);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(day);
+    }
+
+    public Integer getPrimitiveDay() {
+        return day;
+    }
+}

리뷰: Day 객체의 유효성 검사를 OrderValidator에서 진행하게 되면 도메인 내부 상태 데이터의 캡슐화가 깨질 뿐더러 유효성 검사의 양이 많아지면 OrderValidator 객체가 비대해진다는 단점이 있다고 생각합니다.

Day 객체 내부에서 진행하는 것은 어떤가요??
@@ -0,0 +1,74 @@
+package christmas.domain.order;
+
+import christmas.domain.order.menu.Menu;
+import christmas.domain.order.menu.Quantity;
+import christmas.dto.request.OrderRequest;
+import christmas.dto.response.OrderResponse;
+import christmas.util.InputUtil;
+import christmas.validator.OrderValidator;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class Order {
+
+    Map<Menu, Quantity> Order;
+
+    private Order(List<OrderRequest> orderRequests) {
+        Map<Menu, Quantity> order = InputUtil.parseOrderRequests(orderRequests);
+        validate(order);
+        this.Order = new EnumMap<>(order);
+    }
+
+    public static Order create(List<OrderRequest> orderRequests) {
+        return new Order(orderRequests);
+    }
+
+    private void validate(Map<Menu, Quantity> order) {
+        OrderValidator.validateHasOnlyDrink(order);
+        OrderValidator.validateIsTotalQuantityValid(order);
+    }
+
+    public List<OrderResponse> findOrderResponses() {
+        return Order
+                .entrySet()
+                .stream()
+                .map(entry -> OrderResponse.of(entry.getKey(), entry.getValue()))
+                .toList();
+    }
+
+    public int calculateInitialPrice() {
+        return Order
+                .entrySet()
+                .stream()
+                .mapToInt(entry -> getMenuPrice(entry) * getEachQuantity(entry))
+                .sum();
+    }
+
+    private int getMenuPrice(Map.Entry<Menu, Quantity> entry) {
+        return entry.getKey().getPrice();
+    }
+
+    private Integer getEachQuantity(Map.Entry<Menu, Quantity> entry) {
+        return entry.getValue().getPrimitiveQuantity();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Order order = (Order) o;
+        return Objects.equals(getOrder(), order.getOrder());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getOrder());
+    }
+
+    public Map<Menu, Quantity> getOrder() {
+        return new EnumMap<>(Order);
+    }
+}

리뷰: 접근제어자를 default로 선언하신 이유가 궁금합니다!