ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,82 @@
+package store.domain.order;
+
+import java.util.List;
+import store.domain.product.Product;
+
+/**
+ * OrderStatus 클래스는 주문 요청에 대한 결과를 필드로 가지고 있습니다.
+ * 결제에 필요한 정보를 제공하는 것이 책임입니다.
+ */
+public class OrderStatus {
+    private List<Product> products;
+    private final boolean inStock;
+    private final boolean canGetFreeItem;
+    private final int promotionCanAppliedCount;
+
+    public OrderStatus(List<Product> products, boolean inStock, boolean canGetFreeItem, int promotionCanAppliedCount) {
+        this.products = products;
+        this.inStock = inStock;
+        this.canGetFreeItem = canGetFreeItem;
+        this.promotionCanAppliedCount = promotionCanAppliedCount;
+    }
+
+    public OrderStatus(List<Product> products, boolean inStock) {
+        this(products, inStock, false, 0);
+    }
+
+    public Product getFirstProduct() {
+        return products.getFirst();
+    }
+
+    public void removeNormalProduct() {
+        products = products.stream().filter(Product::isPromotedProduct).toList();
+    }
+
+    public List<Product> getMultipleProducts() {
+        return products;
+    }
+
+    public boolean isCanGetFreeItem() {
+        return canGetFreeItem;
+    }
+
+    public boolean isProductFound() {
+        return !products.isEmpty();
+    }
+
+    public boolean isInStock() {
+        return inStock;
+    }
+
+    public boolean hasPromotionProduct() {
+        return products.stream().anyMatch(Product::isPromotedProduct);
+    }
+
+    public int getNotAppliedItemCount(int quantity) {
+        if (promotionCanAppliedCount < quantity) {
+            return quantity - promotionCanAppliedCount;
+        }
+        return 0;
+    }
+
+    public boolean isMultipleStock() {
+        return products.size() == 2;
+    }
+
+    public static OrderStatus inMultipleNormalProductStock(List<Product> products) {
+        return new OrderStatus(products, true);
+    }
+
+    public static OrderStatus outOfStock(List<Product> products) {
+        return new OrderStatus(products, false);
+    }
+
+    public static OrderStatus inOnlyNormalStock(Product product) {
+        return new OrderStatus(List.of(product), true);
+    }
+
+    public static OrderStatus inOnlyPromotionStock(Product product, boolean canGetFreeItem,
+                                                   int promotionCanAppliedCount) {
+        return new OrderStatus(List.of(product), true, canGetFreeItem, promotionCanAppliedCount);
+    }
+}

리뷰: 줄내림을 적용하는 게 가독성이 더 좋을 거 같아요!

```java
products = products.stream().
    filter(Product::isPromotedProduct)
    .toList();
```
@@ -0,0 +1,31 @@
+package store.domain.receipt;
+
+/**
+ * FreeItem 은 Receipt(영수증) 정보에 저장되기 위해 존재하는 객체로, 결론적으로 무료로 제공받은 프로모션 품목의 결과를 저장합니다.
+ * 무료로 제공받은 프로모션 품목의 정보를 저장하고, 제공하는 것이 책임입니다.
+ *
+ * @see Receipt
+ */
+public class FreeItem {
+    private final String name;
+    private final int quantity;
+    private final int totalDiscount;
+
+    public FreeItem(String name, int quantity, int totalDiscount) {
+        this.name = name;
+        this.quantity = quantity;
+        this.totalDiscount = totalDiscount;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public int getTotalDiscount() {
+        return totalDiscount;
+    }
+}

리뷰: 이 부분이 저도 가장 큰 고민이었습니다. 현재 간단한 메시지로 get 함수로 private 접근제어자로 명시된 데이터를 불러오는 역할을 합니다. 하지만 3차 공통 피드백에 이에 대한 사용을 지양하라는 내용이 있었는데 그 부분에 대해서는 어떻게 생각하시나요?
@@ -0,0 +1,79 @@
+package store.util;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Scanner;
+import store.domain.Stock;
+import store.domain.promotion.Promotion;
+import store.domain.promotion.PromotionParameter;
+import store.domain.promotion.Promotions;
+import store.domain.product.Product;
+import store.domain.product.ProductParameter;
+
+/**
+ * StoreInitializer 는 상품과 프로모션 파일을 읽고, 재고 정보를 생성하는 유틸성 클래스입니다.
+ */
+public class StoreInitializer {
+    private Promotions promotions;
+
+    public Stock initStock() throws FileNotFoundException {
+        promotions = readPromotionFile();
+        Stock stock = readProductFile();
+        stock.generateNormalProductFromOnlyPromotionProduct();
+        return stock;
+    }
+
+    private Promotions readPromotionFile() throws FileNotFoundException {
+        Scanner scanner = new Scanner(new File("src/main/resources/promotions.md"));
+        Validator.checkPromotionFirstLine(scanner.nextLine());
+        return readPromotionLines(scanner);
+    }
+
+    private Promotions readPromotionLines(Scanner scanner) {
+        List<Promotion> promotions = new ArrayList<>();
+        while (scanner.hasNext()) {
+            String currentLine = scanner.nextLine();
+            promotions.add(initPromotionThat(currentLine));
+        }
+        return new Promotions(promotions);
+    }
+
+    private Promotion initPromotionThat(String line) {
+        Validator.checkPromotionInitLine(line);
+        List<String> parameters = List.of(line.split(","));
+        PromotionParameter promotionParameter = new PromotionParameter(parameters);
+        return new Promotion(promotionParameter);
+    }
+
+    private Stock readProductFile() throws FileNotFoundException {
+        Scanner scanner = new Scanner(new File("src/main/resources/products.md"));
+        Validator.checkProductFirstLine(scanner.nextLine());
+        return readProductLines(scanner);
+    }
+
+    private Stock readProductLines(Scanner scanner) {
+        List<Product> products = new ArrayList<>();
+        while (scanner.hasNext()) {
+            String currentLine = scanner.nextLine();
+            products.add(initProductThat(currentLine));
+        }
+
+        return new Stock(products);
+    }
+
+    private Product initProductThat(String line) {
+        Validator.checkProductInitLine(line);
+        List<String> parameters = List.of(line.split(","));
+        ProductParameter productParameter = new ProductParameter(parameters);
+        Promotion promotion = findPromotionByName(productParameter.getPromotionName());
+        return new Product(productParameter, promotion);
+    }
+
+    private Promotion findPromotionByName(String promotionName) {
+        Optional<Promotion> promotion = promotions.findPromotionByName(promotionName);
+        return promotion.orElse(null);
+    }
+}

리뷰: System.exit()을 사용하지 말라는 요구사항이 있었고, 파일 양식 문제는 프로그램의 정상적인 실행이 불가능한 오류이기에 main에서 catch해서 자연스럽게 프로그램이 종료되도록 했습니다.
@@ -0,0 +1,79 @@
+package store.util;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Scanner;
+import store.domain.Stock;
+import store.domain.promotion.Promotion;
+import store.domain.promotion.PromotionParameter;
+import store.domain.promotion.Promotions;
+import store.domain.product.Product;
+import store.domain.product.ProductParameter;
+
+/**
+ * StoreInitializer 는 상품과 프로모션 파일을 읽고, 재고 정보를 생성하는 유틸성 클래스입니다.
+ */
+public class StoreInitializer {
+    private Promotions promotions;
+
+    public Stock initStock() throws FileNotFoundException {
+        promotions = readPromotionFile();
+        Stock stock = readProductFile();
+        stock.generateNormalProductFromOnlyPromotionProduct();
+        return stock;
+    }
+
+    private Promotions readPromotionFile() throws FileNotFoundException {
+        Scanner scanner = new Scanner(new File("src/main/resources/promotions.md"));
+        Validator.checkPromotionFirstLine(scanner.nextLine());
+        return readPromotionLines(scanner);
+    }
+
+    private Promotions readPromotionLines(Scanner scanner) {
+        List<Promotion> promotions = new ArrayList<>();
+        while (scanner.hasNext()) {
+            String currentLine = scanner.nextLine();
+            promotions.add(initPromotionThat(currentLine));
+        }
+        return new Promotions(promotions);
+    }
+
+    private Promotion initPromotionThat(String line) {
+        Validator.checkPromotionInitLine(line);
+        List<String> parameters = List.of(line.split(","));
+        PromotionParameter promotionParameter = new PromotionParameter(parameters);
+        return new Promotion(promotionParameter);
+    }
+
+    private Stock readProductFile() throws FileNotFoundException {
+        Scanner scanner = new Scanner(new File("src/main/resources/products.md"));
+        Validator.checkProductFirstLine(scanner.nextLine());
+        return readProductLines(scanner);
+    }
+
+    private Stock readProductLines(Scanner scanner) {
+        List<Product> products = new ArrayList<>();
+        while (scanner.hasNext()) {
+            String currentLine = scanner.nextLine();
+            products.add(initProductThat(currentLine));
+        }
+
+        return new Stock(products);
+    }
+
+    private Product initProductThat(String line) {
+        Validator.checkProductInitLine(line);
+        List<String> parameters = List.of(line.split(","));
+        ProductParameter productParameter = new ProductParameter(parameters);
+        Promotion promotion = findPromotionByName(productParameter.getPromotionName());
+        return new Product(productParameter, promotion);
+    }
+
+    private Promotion findPromotionByName(String promotionName) {
+        Optional<Promotion> promotion = promotions.findPromotionByName(promotionName);
+        return promotion.orElse(null);
+    }
+}

리뷰: 좋은 피드백 감사드립니다 !!
@@ -0,0 +1,31 @@
+package store.domain.receipt;
+
+/**
+ * FreeItem 은 Receipt(영수증) 정보에 저장되기 위해 존재하는 객체로, 결론적으로 무료로 제공받은 프로모션 품목의 결과를 저장합니다.
+ * 무료로 제공받은 프로모션 품목의 정보를 저장하고, 제공하는 것이 책임입니다.
+ *
+ * @see Receipt
+ */
+public class FreeItem {
+    private final String name;
+    private final int quantity;
+    private final int totalDiscount;
+
+    public FreeItem(String name, int quantity, int totalDiscount) {
+        this.name = name;
+        this.quantity = quantity;
+        this.totalDiscount = totalDiscount;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public int getTotalDiscount() {
+        return totalDiscount;
+    }
+}

리뷰: FreeItem은 Receipt 객체의 데이터 저장소 정도로만 사용되는 객체이긴합니다. 저는 현재로썬 이 문제를 getter 없이 해결하는 마땅한 방법이 떠오르지 않습니다. 3차 공통 피드백의 getter을 지양하라는 내용은 아마 데이터와 그 데이터를 사용하는 로직을 같은 곳에 두라는 말을 강력하게 학습시키기 위해 한 말이라고 생각하고, 아예 설계 과정에서 getter을 사용하지 않을 순 없다고 생각합니다.
@@ -0,0 +1,82 @@
+package store.domain.order;
+
+import java.util.List;
+import store.domain.product.Product;
+
+/**
+ * OrderStatus 클래스는 주문 요청에 대한 결과를 필드로 가지고 있습니다.
+ * 결제에 필요한 정보를 제공하는 것이 책임입니다.
+ */
+public class OrderStatus {
+    private List<Product> products;
+    private final boolean inStock;
+    private final boolean canGetFreeItem;
+    private final int promotionCanAppliedCount;
+
+    public OrderStatus(List<Product> products, boolean inStock, boolean canGetFreeItem, int promotionCanAppliedCount) {
+        this.products = products;
+        this.inStock = inStock;
+        this.canGetFreeItem = canGetFreeItem;
+        this.promotionCanAppliedCount = promotionCanAppliedCount;
+    }
+
+    public OrderStatus(List<Product> products, boolean inStock) {
+        this(products, inStock, false, 0);
+    }
+
+    public Product getFirstProduct() {
+        return products.getFirst();
+    }
+
+    public void removeNormalProduct() {
+        products = products.stream().filter(Product::isPromotedProduct).toList();
+    }
+
+    public List<Product> getMultipleProducts() {
+        return products;
+    }
+
+    public boolean isCanGetFreeItem() {
+        return canGetFreeItem;
+    }
+
+    public boolean isProductFound() {
+        return !products.isEmpty();
+    }
+
+    public boolean isInStock() {
+        return inStock;
+    }
+
+    public boolean hasPromotionProduct() {
+        return products.stream().anyMatch(Product::isPromotedProduct);
+    }
+
+    public int getNotAppliedItemCount(int quantity) {
+        if (promotionCanAppliedCount < quantity) {
+            return quantity - promotionCanAppliedCount;
+        }
+        return 0;
+    }
+
+    public boolean isMultipleStock() {
+        return products.size() == 2;
+    }
+
+    public static OrderStatus inMultipleNormalProductStock(List<Product> products) {
+        return new OrderStatus(products, true);
+    }
+
+    public static OrderStatus outOfStock(List<Product> products) {
+        return new OrderStatus(products, false);
+    }
+
+    public static OrderStatus inOnlyNormalStock(Product product) {
+        return new OrderStatus(List.of(product), true);
+    }
+
+    public static OrderStatus inOnlyPromotionStock(Product product, boolean canGetFreeItem,
+                                                   int promotionCanAppliedCount) {
+        return new OrderStatus(List.of(product), true, canGetFreeItem, promotionCanAppliedCount);
+    }
+}

리뷰: 좋은 피드백인 것 같습니다. 줄 길이 제한에 도달하지 않았기에 굳이 개행을 하지 않은것인데, 이렇게 예시를 보여주시니 .toList() 가 한눈에 보이는 게 훨씬 가독성이 좋아 보이네요. 감사합니다.
@@ -0,0 +1,35 @@
+package store.domain.order;
+
+/**
+ * OrderItem 은 사용자의 주문 내역(주문한 상품 이름, 수량)을 필드로 가지고 있습니다.
+ * 사용자의 주문 내역을 관리하는 것이 책임입니다.
+ */
+public class OrderItem {
+    private final String itemName;
+    private int quantity;
+
+    public OrderItem(String itemName, int quantity) {
+        this.itemName = itemName;
+        this.quantity = quantity;
+    }
+
+    public String getItemName() {
+        return itemName;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void addQuantity(int amount) {
+        if (amount > 0) {
+            quantity += amount;
+        }
+    }
+
+    public void subQuantity(int amount) {
+        if (amount > 0) {
+            quantity -= amount;
+        }
+    }
+}

리뷰: 아래 말씀드린 것과 같이 모든 설계 과정에서 get과 set을 사용하지 않을 순 없다고 생각합니다. 저도 공통 피드백을 듣고 사용하지 않으려고 노력했으나, 마땅한 방법이 떠오르지 않았고 누군가 마땅한 방법을 안다면 알려주면 좋겠다는 간절한 마음입니다. 아마 우아한테크코스에서 그와 같이 말한것은 객체는 자기 자신의 데이터에 대해 처리를 해야 할 책임이 있는데, get과 set으로 다른 객체에서 데이터 원본 자체를 얻도록 만들면 데이터에 대한 처리를 위임하는 것과 같기 때문에, 객체의 본분을 잃어버리기 때문이라고 생각합니다.

그렇게 해석해봤을 때 OrderItem은 단순히 데이터를 전달하는 구조체이지, 자신의 데이터에 대해 책임을 지는 객체라고 할 수 없을 것 같습니다. 모든 프로그램은 100% 객체 지향으로 이루어지기 힘듭니다. 사실 저희는 프로그램을 작성하면서 다양한 설계 패러다임을 알맞게 적용하고 있습니다. OrderItem 처럼 사용자의 입력을 아이템 명과 수량으로 나눠서 저장해놓는 용도의 구조는 제가 한 방법처럼 get, set을 사용하는 것이 제일 효율적이라고 생각하고, 이것을 자신의 데이터에 대해 책임지고 데이터와 로직이 응집되어 있는 객체로 전환하는 것은 오히려 프로젝트의 복잡성을 올리고 직관적이지 않은 설계라고 생각합니다.

우아한테크코스에서 제시하는 다양한 피드백들은 사실 의문의 여지가 있습니다.
depth를 2 초과하지 말라던가, 메서드 라인을 10줄을 넘기지 말라던지요..! 물론 특정 상황에서는 depth를 2 초과하는 것이 가독성이 더 좋을 때가 있고, 라인 10줄을 넘지 않기 위해 메서드를 분리하는 것이 가독성에 안좋은 영향을 끼칠 수 있습니다.
하지만 결과적으로 이러한 훈련들로 인해서 저희는 depth를 2 초과하는 행동을 할 때, 메서드 라인을 10줄 넘기는 행동을 할 때, 또 이번처럼 getter와 setter을 쓸 때 한번 멈칫하게 됩니다. "이것이 과연 올바른 설계일까?" 라고 생각하면서요. 저는 이것이 우아한테크코스 피드백의 의도라고 생각합니다. 그저 한번 멈칫하고 스스로 생각하는 시간을 주는거죠. 그리고 getter와 setter 사용 이전에 멈칫 해봤을 때 이건 도저히 getter와 setter을 사용하지 않을 수 없고 오히려 사용하면 더 복잡한 설계를 초래하겠다는 결론에 도달하여 사용한 것입니다.
@@ -0,0 +1,63 @@
+package store;
+
+import store.domain.Stock;
+import store.domain.receipt.Receipt;
+import store.domain.order.OrderItems;
+import store.view.View;
+
+public class StoreController {
+    private final StoreService storeService;
+    private final Stock stock;
+
+    public StoreController(StoreService storeService, Stock stock) {
+        this.storeService = storeService;
+        this.stock = stock;
+    }
+
+    public void run() {
+        do {
+            runStoreProcess();
+        } while (askContinueShopping());
+    }
+
+    private void runStoreProcess() {
+        printGreetingMessage();
+        printStockStatus(stock);
+        OrderItems orderItems = getOrderItems();
+        try {
+            Receipt receipt = proceedPurchase(orderItems);
+            printReceipt(receipt);
+        } catch (Exception e) {
+            printErrorMessage(e.getMessage());
+        }
+    }
+
+    private void printGreetingMessage() {
+        View.getInstance().printGreetingMessage();
+    }
+
+    private void printStockStatus(Stock stock) {
+        View.getInstance().printStockStatus(stock.getProducts());
+    }
+
+    private OrderItems getOrderItems() {
+        String input = View.getInstance().promptBuyItems();
+        return storeService.getOrderItems(input);
+    }
+
+    private Receipt proceedPurchase(OrderItems orderItems) {
+        return storeService.proceedPurchase(stock, orderItems);
+    }
+
+    private void printReceipt(Receipt receipt) {
+        View.getInstance().printReceipt(receipt);
+    }
+
+    private boolean askContinueShopping() {
+        return View.getInstance().promptContinueShopping();
+    }
+
+    private void printErrorMessage(String errorMessage) {
+        View.getInstance().printErrorMessage(errorMessage);
+    }
+}

리뷰: 헉 감사합니다... ! 아직 부족한 스파게티인데 ... :(
@@ -1 +1,90 @@
 # java-convenience-store-precourse
+## 프로젝트 설명
+구매자의 할인 혜택과 재고 상황을 고려하여 최종 결제 금액을 계산하고 안내하는 결제 시스템을 구현하는 과제입니다.
+
+사용자가 입력한 상품별 가격과 수량을 곱하여 계산하고, 최종 결제 금액을 계산하여 영수증을 출력해야 합니다.  
+각 상품의 재고를 고려하여 결제 가능 여부를 파악해야 하고, 프로모션이 적용되어 있는 상품들이 있습니다.  
+프로모션은 N개 구매 시 1개 무료 증정의 형태로 진행되고, 오늘 날짜가 프로모션 기간 내라면 할인을 적용합니다.  
+멤버십 회원은 프로모션이 적용되지 않은 금액의 30%를 할인받습니다. 최대 할인 한도는 8,000원입니다.  
+영수증은 고객의 구매 내역과 할인을 요약하여 보기 좋게 출력해야 합니다.
+
+<br>
+
+## 기능 요구 사항에 기재되지 않아서 스스로 판단한 내용
+이번 과제는 기능 요구 사항에 기재되지 않은 애매한 내용들이 꽤 있었습니다.  
+이에 대해 스스로 판단한 요구 사항을 알려드립니다.  
+
+1. 프로모션 기간이 아직 시작되지 않았거나 이미 종료되었다면, 해당 상품은 프로모션이 적용되지 않은 일반 상품으로 취급되는 것이지 재고 자체가 사라지진 않습니다. 즉 일반 재고로 취급합니다.
+2. 멤버십 회원은 프로모션 재고 상품의 총 구매가를 제외한 구매 비용의 30%를 할인받습니다.
+3. 프로모션 2+1 콜라가 7개 있고, 사용자가 7개를 구매하길 원한다면 실제로 프로모션을 적용받을 수 있는 콜라는 6개까지이지만, 프로모션 재고가 부족한 것은 아니기에 7개를 모두 구매할 수 있습니다. (우아한테크코스의 예시를 참고하면, 프로모션 재고가 부족하여 일반 재고까지 구매해야 하는 상황에서만 프로모션이 미적용 되는 상품에 대해서 안내를 합니다.  예를 들어서 프로모션 2+1 콜라가 10개 있고, 일반 재고의 콜라가 10개 있는 상황에서 사용자가 12개를 구매하려고 한다면, 프로모션을 적용할 수 있는 실제 재고 개수는 9개 이므로 3개에 대해 프로모션 미적용 안내를 합니다)
+4. 프로모션이 적용된 모든 상품은 프로모션이 적용되지 않은 버전의 상품 정보도 표시해야 하고, 프로모션 미적용 상품이 존재하지 않는다면 "재고 없음" 을 표시해야 합니다.
+
+<br>
+
+## 설계 과정
+객체지향 설계에 있어서 가장 중요한 것은 협력을 위해 어떤 책임과 역할이 필요한지 이해하는 것이라고 생각합니다.  
+따라서 설계의 방향을 잡기 위해 필요한 역할과 책임 그리고 메시지를 정리해 봤습니다.  
+이후 정리한 내용을 통해 도출한 대략적인 그림을 참고해서 구현할 기능 목록을 하단에 요약했습니다.
+
+
+> 이해한 과제 내용을 대략적으로 정리하는 것은 필요한 역할과 책임을 이해하는 데에 도움이 된다고 생각합니다.  
+> 이렇게 정리한 내용에서 영감을 받아 구현할 기능 목록을 대략적으로나마 작성할 수 있었습니다.  
+> 물론 구현 중 계획했던 내용이 틀어질 수 있지만, 프로젝트를 시작하는 데에 도움이 되었습니다.
+
+<br>
+
+### 필요한 역할 및 기능
+- [x] 상품의 정보(상품명과 가격)을 가지고 있는 역할이 있어야 한다.
+- [x] 상품별 재고 정보를 가지고 있는 역할이 있어야 한다.
+- [x] 재고 수량을 고려하여 결제 가능 여부를 확인할 수 있어야 한다.
+- [x] 상품이 구입될 때마다, 결제된 수량만큼 해당 상품의 재고에서 차감해야 한다.
+- [x] 상품에 프로모션을 적용할 수 있어야 한다. 오늘 날짜가 프로모션 기간인 경우에만 할인을 적용해야 한다.
+- [x] 프로모션 기간 중이라면 프로모션 재고를 우선적으로 차감해야 한다.
+- [x] 멤버쉽 회원은 프로모션 미적용 금액의 30%를 할인받는다.
+- [x] 멤버쉽 할인의 최대 한도는 8,000원이다.
+- [x] 영수증 기능이 필요하다.
+
+
+### 메시지 정리
+- [x] 환영 문구를 출력하라 -> (출력 역할) -> OutputView  
+- [x] 사용자에게 상품의 가격과 수량을 입력받아라 -> (입력 역할) -> InputView  
+- [x] 상품 재고를 초기화하라 -> (파일 입출력해서 재고 초기화하는 역할) -> StoreInitializer  
+- [x] 상품 재고를 출력하라 -> OutputView -> (상품 재고를 갖고있는 역할) -> StoreStock  
+- [x] 결제 가능 여부를 파악하라 -> (상품 재고를 갖고있는 역할) -> StoreStock  
+- [x] 프로모션 상품인지 확인하라 -> (상품 역할) -> StoreItem  
+- [x] 프로모션 재고가 충분한지 파악하라 -> (상품 역할) -> StoreItem
+
+
+<br>
+
+
+## 구현할 기능 목록
+### 1. 출력 역할
+- [x] 출력 역할의 객체를 만든다
+- [x] 환영 문구를 출력하는 기능
+- [x] 재고 정보를 출력하는 기능
+- [x] 프롬프트 메시지를 출력하는 기능
+- [x] 영수증 내용을 출력하는 기능
+
+### 2. 입력 역할
+- [x] 입력 역할의 객체를 만든다
+- [x] 사용자에게 구매할 상품명과 수량을 입력받는 기능
+- [x] Y/N 형태로 입력받는 기능
+- [x] 입력 값이 잘못되었을 경우 오류 메시지와 함께 재입력 받는 기능
+
+### 3. 파일 입출력 역할
+- [x] 파일 입출력 역할의 객체를 만든다
+- [x] .md 파일을 읽어서 상품 재고를 초기화하는 기능
+
+### 4. 상품 역할
+- [x] 상품의 정보(상품명과 가격 등)을 가지고 있는 객체를 만든다
+- [x] 상품에 프로모션 정보를 포함할 수 있는 기능을 만든다
+
+### 5. 재고 역할
+- [x] 상품의 정보들을 가지고, 재고 역할을 하는 객체를 만든다
+- [x] 재고에서 특정 상품을 찾는 기능
+- [x] 프로모션 상품인지, 비 프로모션 상품인지 구분하는 기능
+
+### 6. 영수증 역할
+- [x] 영수증 역할의 객체를 만든다
+- [x] 구입 내역을 입력받아서 저장하는 기능
\ No newline at end of file

리뷰: 블로그에 회고 글을 작성했는데, 아래 링크에서 설계 과정을 참고하시면 답변이 될 것 같습니다!
https://hacanna42.tistory.com/304
@@ -1 +1,90 @@
 # java-convenience-store-precourse
+## 프로젝트 설명
+구매자의 할인 혜택과 재고 상황을 고려하여 최종 결제 금액을 계산하고 안내하는 결제 시스템을 구현하는 과제입니다.
+
+사용자가 입력한 상품별 가격과 수량을 곱하여 계산하고, 최종 결제 금액을 계산하여 영수증을 출력해야 합니다.  
+각 상품의 재고를 고려하여 결제 가능 여부를 파악해야 하고, 프로모션이 적용되어 있는 상품들이 있습니다.  
+프로모션은 N개 구매 시 1개 무료 증정의 형태로 진행되고, 오늘 날짜가 프로모션 기간 내라면 할인을 적용합니다.  
+멤버십 회원은 프로모션이 적용되지 않은 금액의 30%를 할인받습니다. 최대 할인 한도는 8,000원입니다.  
+영수증은 고객의 구매 내역과 할인을 요약하여 보기 좋게 출력해야 합니다.
+
+<br>
+
+## 기능 요구 사항에 기재되지 않아서 스스로 판단한 내용
+이번 과제는 기능 요구 사항에 기재되지 않은 애매한 내용들이 꽤 있었습니다.  
+이에 대해 스스로 판단한 요구 사항을 알려드립니다.  
+
+1. 프로모션 기간이 아직 시작되지 않았거나 이미 종료되었다면, 해당 상품은 프로모션이 적용되지 않은 일반 상품으로 취급되는 것이지 재고 자체가 사라지진 않습니다. 즉 일반 재고로 취급합니다.
+2. 멤버십 회원은 프로모션 재고 상품의 총 구매가를 제외한 구매 비용의 30%를 할인받습니다.
+3. 프로모션 2+1 콜라가 7개 있고, 사용자가 7개를 구매하길 원한다면 실제로 프로모션을 적용받을 수 있는 콜라는 6개까지이지만, 프로모션 재고가 부족한 것은 아니기에 7개를 모두 구매할 수 있습니다. (우아한테크코스의 예시를 참고하면, 프로모션 재고가 부족하여 일반 재고까지 구매해야 하는 상황에서만 프로모션이 미적용 되는 상품에 대해서 안내를 합니다.  예를 들어서 프로모션 2+1 콜라가 10개 있고, 일반 재고의 콜라가 10개 있는 상황에서 사용자가 12개를 구매하려고 한다면, 프로모션을 적용할 수 있는 실제 재고 개수는 9개 이므로 3개에 대해 프로모션 미적용 안내를 합니다)
+4. 프로모션이 적용된 모든 상품은 프로모션이 적용되지 않은 버전의 상품 정보도 표시해야 하고, 프로모션 미적용 상품이 존재하지 않는다면 "재고 없음" 을 표시해야 합니다.
+
+<br>
+
+## 설계 과정
+객체지향 설계에 있어서 가장 중요한 것은 협력을 위해 어떤 책임과 역할이 필요한지 이해하는 것이라고 생각합니다.  
+따라서 설계의 방향을 잡기 위해 필요한 역할과 책임 그리고 메시지를 정리해 봤습니다.  
+이후 정리한 내용을 통해 도출한 대략적인 그림을 참고해서 구현할 기능 목록을 하단에 요약했습니다.
+
+
+> 이해한 과제 내용을 대략적으로 정리하는 것은 필요한 역할과 책임을 이해하는 데에 도움이 된다고 생각합니다.  
+> 이렇게 정리한 내용에서 영감을 받아 구현할 기능 목록을 대략적으로나마 작성할 수 있었습니다.  
+> 물론 구현 중 계획했던 내용이 틀어질 수 있지만, 프로젝트를 시작하는 데에 도움이 되었습니다.
+
+<br>
+
+### 필요한 역할 및 기능
+- [x] 상품의 정보(상품명과 가격)을 가지고 있는 역할이 있어야 한다.
+- [x] 상품별 재고 정보를 가지고 있는 역할이 있어야 한다.
+- [x] 재고 수량을 고려하여 결제 가능 여부를 확인할 수 있어야 한다.
+- [x] 상품이 구입될 때마다, 결제된 수량만큼 해당 상품의 재고에서 차감해야 한다.
+- [x] 상품에 프로모션을 적용할 수 있어야 한다. 오늘 날짜가 프로모션 기간인 경우에만 할인을 적용해야 한다.
+- [x] 프로모션 기간 중이라면 프로모션 재고를 우선적으로 차감해야 한다.
+- [x] 멤버쉽 회원은 프로모션 미적용 금액의 30%를 할인받는다.
+- [x] 멤버쉽 할인의 최대 한도는 8,000원이다.
+- [x] 영수증 기능이 필요하다.
+
+
+### 메시지 정리
+- [x] 환영 문구를 출력하라 -> (출력 역할) -> OutputView  
+- [x] 사용자에게 상품의 가격과 수량을 입력받아라 -> (입력 역할) -> InputView  
+- [x] 상품 재고를 초기화하라 -> (파일 입출력해서 재고 초기화하는 역할) -> StoreInitializer  
+- [x] 상품 재고를 출력하라 -> OutputView -> (상품 재고를 갖고있는 역할) -> StoreStock  
+- [x] 결제 가능 여부를 파악하라 -> (상품 재고를 갖고있는 역할) -> StoreStock  
+- [x] 프로모션 상품인지 확인하라 -> (상품 역할) -> StoreItem  
+- [x] 프로모션 재고가 충분한지 파악하라 -> (상품 역할) -> StoreItem
+
+
+<br>
+
+
+## 구현할 기능 목록
+### 1. 출력 역할
+- [x] 출력 역할의 객체를 만든다
+- [x] 환영 문구를 출력하는 기능
+- [x] 재고 정보를 출력하는 기능
+- [x] 프롬프트 메시지를 출력하는 기능
+- [x] 영수증 내용을 출력하는 기능
+
+### 2. 입력 역할
+- [x] 입력 역할의 객체를 만든다
+- [x] 사용자에게 구매할 상품명과 수량을 입력받는 기능
+- [x] Y/N 형태로 입력받는 기능
+- [x] 입력 값이 잘못되었을 경우 오류 메시지와 함께 재입력 받는 기능
+
+### 3. 파일 입출력 역할
+- [x] 파일 입출력 역할의 객체를 만든다
+- [x] .md 파일을 읽어서 상품 재고를 초기화하는 기능
+
+### 4. 상품 역할
+- [x] 상품의 정보(상품명과 가격 등)을 가지고 있는 객체를 만든다
+- [x] 상품에 프로모션 정보를 포함할 수 있는 기능을 만든다
+
+### 5. 재고 역할
+- [x] 상품의 정보들을 가지고, 재고 역할을 하는 객체를 만든다
+- [x] 재고에서 특정 상품을 찾는 기능
+- [x] 프로모션 상품인지, 비 프로모션 상품인지 구분하는 기능
+
+### 6. 영수증 역할
+- [x] 영수증 역할의 객체를 만든다
+- [x] 구입 내역을 입력받아서 저장하는 기능
\ No newline at end of file

리뷰: 추가로 설계는 전체적인 틀을 텍스트로 써내려가는 것이 아닌, 제가 스스로 감을 잡는식으로 하고 나머지는 구현 과정에서 채워 나갔습니다.
물론 이 방법으로 구현할 때 조금의 착오가 있긴했지만 나쁘지 않은 설계 방식 같습니다.