@@ -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);
+    }
+
+}

리뷰: @plan11plan 

싱글톤을 도입하게 된 과정은 다음과 같아요.

> 1. 3개의 컨트롤러가 모두 동일한 데이터(List<PurchaseProduct>)를 사용하고 있음
> 2. 해당 데이터를 매번 컨트롤러끼리 넘겨주는것 보다 더 나은 방법이 없을까 고민하게 됨
> 3. 그 과정 속에서 리액트 리덕스를 떠올리게 됨, 그래서 List<PurchaseProduct>의 상태를 관리하는 PurchaseState 클래스를 따로 두게 됨
> 4. PurchaseState가 관리하는 List<PurchaseProduct>은 애플리케이션 전역에서 동일한 데이터를 반환해야함
> 5. 따라서 PurchaseState은 단 하나의 인스턴스만 존재해야 하므로 싱글톤으로 구현하게 됨

하지만 이렇게 애플리케이션에서 전역으로 사용하는 싱글톤 객체의 경우 필드에 상태를 가지면, 나중에 버그가 터졌을때 디버깅하기 힘들다는 단점도 존재해요! 그래서 돌이켜보면 이렇게 싱글톤으로 작성해서 컨트롤러의 코드들이 이쁘게 작성이 됐지만, 조금은 아쉬운 설계라고 생각이 드네요 ㅎㅎ

---

```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;
    }
```
그리고 짚어주신 요 부분은 PurchaseProduct를 사용하고 있지 않기 때문에, 싱글톤 객체에 저장하는 부분이 없어요
아마 제가 네이밍을 잘못해서 헷갈리신것 같아요 ㅎㅎ!