@@ -0,0 +1,78 @@
+package store.inventory.promotion;
+
+import static store.common.Constants.EXCEPT_LINE_COUNT;
+import static store.common.Constants.INVALID_INPUT_ERROR;
+import static store.common.Constants.WORD_DELIMITER;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+import store.inventory.product.Product;
+
+public class PromotionProcessor {
+    private static final String PROMOTIONS_FILE_PATH = "src/main/resources/promotions.md";
+    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final String NOT_PRODUCTION = "null";
+    private static final int PROMOTION_NAME_INDEX = 0;
+    private static final int PROMOTION_BUY_INDEX = 1;
+    private static final int PROMOTION_GET_INDEX = 2;
+    private static final int PROMOTION_START_DATE_INDEX = 3;
+    private static final int PROMOTION_END_DATE_INDEX = 4;
+
+    private final Map<String, PromotionType> promotions = new HashMap<>();
+    private final Set<String> promotionProducts = new HashSet<>();
+
+    public PromotionProcessor() {
+        load();
+    }
+
+    public boolean isCurrentPromotion(String promotionName) {
+        return (promotions.containsKey(promotionName)
+                && promotions.get(promotionName) != PromotionType.NOT_NOW)
+                || promotionName.equals(NOT_PRODUCTION);
+    }
+
+    public PromotionType getPromotionType(String promotionName) {
+        PromotionType promotionType = promotions.get(promotionName);
+        if(promotionName.equals(NOT_PRODUCTION)) {
+            promotionType = PromotionType.NONE;
+        }
+        return promotionType;
+    }
+
+    public void validateDuplicated(Product product) {
+        if (product.getPromotionType() != PromotionType.NONE
+                && !promotionProducts.add(product.getName())) {
+            throw new IllegalStateException(INVALID_INPUT_ERROR);
+        }
+    }
+
+    private void load() {
+        try (Stream<String> lines = Files.lines(Paths.get(PROMOTIONS_FILE_PATH))) {
+            lines.skip(EXCEPT_LINE_COUNT)
+                    .map(this::parse)
+                    .forEach(promotion ->
+                            promotions.put(promotion.getName(), PromotionType.from(promotion))
+                    );
+        }
+        catch (IOException e) {
+            throw new IllegalArgumentException(INVALID_INPUT_ERROR);
+        }
+    }
+
+    private Promotion parse(String line) {
+        String[] promotionInfo = line.split(WORD_DELIMITER);
+        return new Promotion(promotionInfo[PROMOTION_NAME_INDEX],
+                Integer.parseInt(promotionInfo[PROMOTION_BUY_INDEX]),
+                Integer.parseInt(promotionInfo[PROMOTION_GET_INDEX]),
+                LocalDate.parse(promotionInfo[PROMOTION_START_DATE_INDEX], FORMATTER),
+                LocalDate.parse(promotionInfo[PROMOTION_END_DATE_INDEX], FORMATTER));
+    }
+}

리뷰: 처음 코드를 짤 때는 프로모션 정보를 파일에서 읽어오고(PromotionProcessor), 파싱해온 정보를 임시로 처리하기 위한 구조(Promotion)를 만들면서 클래스가 나뉘었던 것 같아요!

그런데 프로모션을 단순히 읽어오는 것뿐만 아니라, (지금 클래스 내 메서드로 존재하는) 다른 로직들이 필요해지면서 프로모션 관련 처리를 담당하던 PromotionProcessor에 추가 코드가 생겼습니다.

말씀을 듣고 생각해보니, 완성된 코드를 보는 입장에서 책임이 모호하단 느낌이 드네요!
이 부분을 Promotion으로 합치는 것이 좋은 방안이 될 수 있단 생각이 들어요:)

언급해주셔서 감사합니다!