@@ -0,0 +1,125 @@
+import { ERROR_MSG } from '../../constants/PlannerMsg.js';
+import { FOOD_MENU } from '../../constants/FoodMenu.js';
+
+class MenuValidate {
+  #canOrder;
+
+  #userMenu;
+
+  #menu;
+
+  constructor(menu, canOrder = [], userMenu = []) {
+    this.#menu = menu;
+    this.#canOrder = canOrder;
+    this.#userMenu = userMenu;
+  }
+
+  menuCheck() {
+    this.#findingMenu(this.#canOrder, this.#userMenu, this.#menu);
+    this.#userMenu.forEach((order) => {
+      if (!this.#canOrder.includes(order)) {
+        throw new Error(ERROR_MSG.notInMenu);
+      }
+    });
+  }
+
+  #findingMenu() {
+    Object.keys(FOOD_MENU).map((course) =>
+      this.#makeCanOrder(course, this.#canOrder),
+    );
+
+    this.#menu.forEach((order) => this.#userMenu.push(order.split('-')[0]));
+  }
+
+  #makeCanOrder(course) {
+    Object.values(FOOD_MENU[`${course}`]).map((food) =>
+      this.#canOrder.push(food[0]),
+    );
+  }
+
+  menuAmountCheck(regax) {
+    const totalMenuAmount = [];
+    this.#menu.forEach((order) => {
+      this.#amountCheck(order.split('-')[1], regax);
+      totalMenuAmount.push(Number(order.split('-')[1]));
+    });
+
+    this.#totalAmountCheck(totalMenuAmount);
+  }
+
+  #amountCheck(amount, regax) {
+    if (regax.test(Number(amount))) {
+      throw new Error(ERROR_MSG.notInMenu);
+    }
+    if (Number(amount) <= 0) {
+      throw new Error(ERROR_MSG.notInMenu);
+    }
+
+    if (!Number.isSafeInteger(Number(amount))) {
+      throw new Error(ERROR_MSG.notInMenu);
+    }
+  }
+
+  #totalAmountCheck(total) {
+    const checkTotal = total.reduce((sum, amount) => sum + amount);
+    if (checkTotal > 20) {
+      throw new Error(ERROR_MSG.totalError);
+    }
+  }
+
+  formCheck() {
+    const form = /^[가-힣]+-[0-9]+$/;
+    this.#menu.forEach((order) => this.#formRegaxCheck(order, form));
+  }
+
+  #formRegaxCheck(order, form) {
+    if (!form.test(order)) {
+      throw new Error(ERROR_MSG.notInMenu);
+    }
+  }
+
+  duplicateCheck() {
+    if (this.#userMenu.length !== new Set(this.#userMenu).size) {
+      throw new Error(ERROR_MSG.notInMenu);
+    }
+  }
+
+  drinkCheck() {
+    const menuObject = this.#makingMenuObject();
+    if (this.#objectCheck(menuObject)) {
+      throw new Error(ERROR_MSG.drinkError);
+    }
+  }
+
+  #makingMenuObject() {
+    const menuObject = {};
+    Object.keys(FOOD_MENU).forEach((course) => {
+      menuObject[course] = this.#userMenuCourses(course);
+    });
+    return menuObject;
+  }
+
+  #userMenuCourses(course) {
+    const includeCourse = [];
+    Object.values(FOOD_MENU[course]).forEach((eachMenu) => {
+      if (this.#menuIncludes(eachMenu)) {
+        includeCourse.push(eachMenu[0]);
+      }
+    });
+    return includeCourse.length;
+  }
+
+  #menuIncludes(eachMenu) {
+    return this.#userMenu.includes(eachMenu[0]);
+  }
+
+  #objectCheck(menuObject) {
+    const { appetizer, main, dessert, drink } = menuObject;
+    if (appetizer === 0 && main === 0 && dessert === 0 && drink !== 0) {
+      return true;
+    }
+    return false;
+  }
+}
+
+export default MenuValidate;

리뷰: 배열을 자주 사용하신 것 같아요! 저도 이번에 미션하면서 Array 랑 Map 중에서 어떤 걸 사용할 지 고민했었는데요, 저한테는 사실 Map 이 익숙한 개념은 아니여서 백엔드분들한테 물어봤는데 모두 배열과 맵 둘 중에 골라야한다면 맵이 더 효율적이라고 하시더라구요! 배열도 index로 값을 가져올 수 있고, Map 도 key로 값을 가져오는데, 배열은 해당index까지 0부터 시작해야한다고 하더라구요. 반면에 맵은 한번에 값을 가져와서 시간복잡도가 O(1) 이라고 합니다!