@@ -0,0 +1,67 @@
+package store.product.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.error.ErrorMessage;
+import store.product.domain.PurchaseProduct;
+import store.product.dto.PurchaseProductRequest;
+import store.product.service.ProductService;
+import store.product.view.ProductOutputView;
+import store.state.PurchaseState;
+import store.util.LoopTemplate;
+import store.util.StringParser;
+import store.util.StringValidator;
+import store.view.InputView;
+
+public class ProductController {
+
+    private final InputView inputView;
+    private final ProductOutputView outputView;
+    private final ProductService productService;
+
+    public ProductController(final InputView inputView, final ProductOutputView outputView,
+                             final ProductService productService) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.productService = productService;
+    }
+
+    public void run() {
+        responseProductStatuses();
+        final List<PurchaseProduct> purchaseProducts = requestPurchaseProduct();
+        final PurchaseState purchaseState = PurchaseState.getInstance();
+        purchaseState.updatePurchaseProducts(purchaseProducts);
+    }
+
+    private void responseProductStatuses() {
+        outputView.printIntro();
+        outputView.printProductStatus(productService.getProductStatuses());
+    }
+
+    private List<PurchaseProduct> requestPurchaseProduct() {
+        return LoopTemplate.tryCatchLoop(() -> {
+            outputView.printAskPurchaseProduct();
+            final List<String> products = inputView.readProducts();
+            final List<PurchaseProductRequest> purchaseProductRequests = generatePurchaseProductRequests(products);
+            return productService.generatePurchaseProducts(purchaseProductRequests);
+        });
+    }
+
+    private List<PurchaseProductRequest> generatePurchaseProductRequests(final List<String> values) {
+        final List<PurchaseProductRequest> purchaseProductRequests = new ArrayList<>();
+        for (String value : values) {
+            StringValidator.validateFormat(value, ErrorMessage.INVALID_FORMAT_PRODUCT_AND_QUANTITY);
+            final String product = StringParser.removePattern(value.trim(), "[\\[\\]]");
+            purchaseProductRequests.add(createPurchaseProductRequest(product));
+        }
+        return purchaseProductRequests;
+    }
+
+    private PurchaseProductRequest createPurchaseProductRequest(final String product) {
+        final String[] splitProduct = product.split("-");
+        final String name = splitProduct[0];
+        final int quantity = StringParser.parseToNumber(splitProduct[1]);
+        return new PurchaseProductRequest(name, quantity);
+    }
+
+}

리뷰: ### 𝐐. 컨트롤러의 흐름 제어를 위해 PurchaseState를 도입하신 배경이 궁금합니다.
컨트롤러의 run() 메서드들이 반환 값 없이 void로 되어있더라구요. 대신 PurchaseState라는 싱글톤이 마치 Repository처럼 동작하면서 컨트롤러 간의 흐름을 제어하고 있습니다.
실제 Convenience 객체에서는 이런 식으로 구현되어 있더라구요:
```java
    public void purchase() {
        do {
            productController.run();
            promotionController.run();
            paymentController.run();
        } while (isRetryPurchase());
        Console.close();
    }

    private boolean isRetryPurchase() {
        return PurchaseState.getInstance().isRetryPurchase();
    }
```
이렇게 보니 각 컨트롤러가 독립적인 모듈처럼 작동하고, 이 모듈들 사이의 데이터 흐름을 PurchaseState가 이어주고 있는 것 같은데요.
이런 구조를 채택하시게 된 특별한 계기나 고민의 과정이 있으셨나요? 처음부터 이런 구조를 계획하신 건지, 아니면 개발 과정에서 자연스럽게 발전된 건지 궁금합니다. 💭

저였다면 Controller에서 응답을 반환하고, 다른 컨트롤러에서 그 응답데이터를 가지고 요청을 시작할 것 같거든요.
하지만 단점이 존재합니다. 이렇게 하면 흐름제어의 부담이 커지기도 하고, 어쩌면 코드를 리팩토링 해야 하는 위험도 있습니다.
장점이라 하면 저는 그저 객체지향적이다..? 라고 밖에 생각이 안들거든요.. 즉, 저도 어떤게 좋은지 잘 모르고 있어요. 왜 이걸 써야만 하는지 정확한 이유를요..!

그래서 이렇게 만드신 이유를 제가 더욱더 궁금해 하는 것 같습니다 🥹

----
추가 내용:

이어서 코드를 읽다보니 구매자에게 상풍목록을 보여줄때 이때, ProductStatus가 조회용 DTO 역할도 하는 것 같습니다.
``` java
    public List<ProductStatus> getProductStatuses() {
        final List<ProductStatus> productStatuses = new ArrayList<>();
        final List<Product> products = productRepository.findAll();
        for (Product product : products) {
            final ProductStatus productStatus = ProductStatus.of(product);
            productStatuses.add(productStatus);
            addNormalProduct(product, productStatuses);
        }
        return productStatuses;
    }

```

사실상 ProductStatus가 DTO 응답 객체 역할을 하고 있었군요. 하지만 싱글톤 저장 기능을 곁들인.
이제야 왜 이렇게 짜셨는지 이해가 가네요. 

혹시 여기에 더 수달님의 의견이 있으신지 여쭤보고 싶습니다.