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

리뷰: p3
클린 코드(Clean Code, 로버트 C. 마틴)에서는 변수명은 코드의 목적과 의도를 명확히 보여줘야 한다고 말합니다.
예를 들어, values 대신 리스트가 무엇을 나타내는지를 더 잘 표현하는 이름을 사용할 수 있습니다.
따라서 values보다는 rawProductInputs나 products와 같이, 리스트에 담긴 데이터가 어떤 맥락에서 사용되는지 보여주는 이름이 더 좋지 않을까 생각이됩니다.

실제로 저도 코드리뷰하면서 values가 뭔지 몰라서 generatePurchaseProductRequests가 사용된 곳으로 가서 인수가 무엇인지 확인했습니다. ㅜ


```suggestion
    private List<PurchaseProductRequest> generatePurchaseProductRequests(final List<String> products) {
        final List<PurchaseProductRequest> purchaseProductRequests = new ArrayList<>();
        for (String product : products) {
            StringValidator.validateFormat(value, ErrorMessage.INVALID_FORMAT_PRODUCT_AND_QUANTITY);
            final String product = StringParser.removePattern(product.trim(), "[\\[\\]]");
            purchaseProductRequests.add(createPurchaseProductRequest(product));
        }
        return purchaseProductRequests;
    }
```