ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,60 @@
+package store.util;
+
+import store.model.domain.customer.Customer;
+import store.model.domain.product.Product;
+import store.model.domain.product.Products;
+
+public class InventoryManager {
+
+	public static void decreaseProductQuantities(Customer customer, Products products) {
+		customer.getBuyingProduct().forEach((productName, quantity) -> {
+			decreaseBuyingProductQuantities(products, productName, quantity);
+		});
+
+		customer.getGiftProduct().forEach((productName, quantity) -> {
+			decreaseGiftProductQuantities(products, productName, quantity);
+		});
+	}
+
+	private static void decreaseBuyingProductQuantities(Products products, String productName, int quantity) {
+		Product promotionProduct = products.findPromotionProductByName(productName);
+		Product regularProduct = products.findRegularProductByName(productName);
+
+		int promotionQuantity = calculatePromotionQuantity(promotionProduct, quantity);
+		int remainingQuantity = quantity - promotionQuantity;
+
+		decreaseProductQuantity(promotionProduct, promotionQuantity);
+		decreaseProductQuantity(regularProduct, remainingQuantity);
+	}
+
+	private static void decreaseGiftProductQuantities(Products products, String productName, int quantity) {
+		Product promotionProduct = products.findPromotionProductByName(productName);
+		Product regularProduct = products.findRegularProductByName(productName);
+
+		int promotionQuantity = calculateGiftPromotionQuantity(promotionProduct, quantity);
+		int remainingQuantity = quantity - promotionQuantity;
+
+		decreaseProductQuantity(promotionProduct, promotionQuantity);
+		decreaseProductQuantity(regularProduct, remainingQuantity);
+	}
+
+	private static int calculatePromotionQuantity(Product promotionProduct, int quantity) {
+		int promotionQuantity = 0;
+		if (promotionProduct != null) {
+			promotionQuantity = Math.min(quantity, promotionProduct.getQuantity());
+		}
+		return promotionQuantity;
+	}
+
+	private static int calculateGiftPromotionQuantity(Product promotionProduct, int quantity) {
+		if (promotionProduct == null)
+			return 0;
+		return Math.min(quantity, promotionProduct.getQuantity());
+	}
+
+	private static void decreaseProductQuantity(Product product, int quantity) {
+		if (product != null && quantity > 0) {
+			product.decreaseQuantity(quantity);
+		}
+	}
+}

리뷰: 이 클래스는 service가 아닌 util인 이유가 뭔지 궁금합니다.
@@ -0,0 +1,17 @@
+package store.constant.file;
+
+public enum FilePath {
+	PRODUCT_FILE_PATH("src/main/resources/products.md"),
+	PROMOTION_FILE_PATH("src/main/resources/promotions.md"),
+	;
+
+	private final String filePath;
+
+	FilePath(String filePath) {
+		this.filePath = filePath;
+	}
+
+	public String getFilePath() {
+		return filePath;
+	}
+}

리뷰: 저는 클래스 내에 상수를 만들었는데 파일 위치도 상수로 처리하셨네요 이방법도 좋아보이네요!
@@ -0,0 +1,82 @@
+package store.util;
+
+import static store.constant.message.ErrorMessage.*;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import store.model.domain.product.Product;
+import store.model.domain.product.Promotion;
+
+public class Parser {
+	public static List<Product> parseProduct(List<String> lines) {
+		return lines.stream()
+			.skip(1)
+			.map(Parser::parseProductLine)
+			.collect(Collectors.toList());
+	}
+
+	public static List<Promotion> parsePromotion(List<String> lines) {
+		return lines.stream()
+			.skip(1)
+			.map(Parser::parsePromotionLine)
+			.collect(Collectors.toList());
+	}
+
+	private static Product parseProductLine(String line) {
+		List<String> columns = parseLine(line);
+		String name = columns.get(0);
+		int price = Integer.parseInt(columns.get(1));
+		int quantity = Integer.parseInt(columns.get(2));
+
+		String promotion = "";
+		if (!"null".equals(columns.get(3))) {
+			promotion = columns.get(3);
+		}
+		return new Product(name, price, quantity, promotion);
+	}
+
+	private static Promotion parsePromotionLine(String line) {
+		List<String> columns = parseLine(line);
+		String name = columns.get(0);
+		int buy = Integer.parseInt(columns.get(1));
+		int get = Integer.parseInt(columns.get(2));
+		LocalDate startDate = LocalDate.parse(columns.get(3));
+		LocalDate endDate = LocalDate.parse(columns.get(4));
+
+		return new Promotion(name, buy, get, startDate, endDate);
+	}
+
+	private static List<String> parseLine(String line) {
+		return Stream.of(line.split(","))
+			.map(String::trim)
+			.collect(Collectors.toList());
+	}
+
+	public static Map<String, Integer> parseBuyProduct(String buyingProduct) {
+		return Arrays.stream(buyingProduct.split("\\],\\["))
+			.map(product -> product.replaceAll("[\\[\\]]", ""))
+			.map(product -> {
+				String[] details = product.split("-");
+				validateInput(details);
+				return details;
+			})
+			.collect(Collectors.toMap(
+				details -> details[0],
+				details -> Integer.parseInt(details[1]),
+				(existing, replacement) -> existing,
+				LinkedHashMap::new
+			));
+	}
+
+	private static void validateInput(String[] details) {
+		if (details.length != 2 || !details[1].matches("\\d+")) {
+			throw new IllegalArgumentException(INVALID_FORMAT.getMessage());
+		}
+	}
+}

리뷰: stream 사용을 하니 간편해 보이네요 배워갑니다
@@ -0,0 +1,25 @@
+package store.service;
+
+import java.io.File;
+import java.util.List;
+
+import store.model.domain.product.Product;
+import store.model.domain.product.Products;
+import store.model.domain.product.Promotion;
+import store.model.domain.product.Promotions;
+import store.util.FileReader;
+import store.util.Parser;
+
+public class FileService {
+	public Products createProducts(String filePath) {
+		List<String> lines = FileReader.readFile(new File(filePath));
+		List<Product> productList = Parser.parseProduct(lines);
+		return new Products(productList);
+	}
+
+	public Promotions createPromotions(String filePath) {
+		List<String> lines = FileReader.readFile(new File(filePath));
+		List<Promotion> promotionList = Parser.parsePromotion(lines);
+		return new Promotions(promotionList);
+	}
+}

리뷰: FileReader.readFile 부분의 메서드를 중복이니까 private으로 빼도 괜찮았을 것 같아요!
@@ -0,0 +1,52 @@
+package store.model.domain.product;
+
+public class Product {
+	private final String name;
+	private final int price;
+	private int quantity;
+	private final String promotion;
+
+	public Product(String name, int price, int quantity, String promotion) {
+		this.name = name;
+		this.price = price;
+		this.quantity = quantity;
+		this.promotion = promotion;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public int getPrice() {
+		return price;
+	}
+
+	public int getQuantity() {
+		return quantity;
+	}
+
+	public String getPromotion() {
+		return promotion;
+	}
+
+	public void decreaseQuantity(int buyQuantity) {
+		if (buyQuantity <= quantity) {
+			quantity -= buyQuantity;
+		}
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder output = new StringBuilder(String.format("- %s %,d원", name, price));
+
+		if (quantity == 0) {
+			output.append(" 재고 없음");
+		}
+		if (quantity != 0) {
+			output.append(String.format(" %d개", quantity));
+		}
+		if (promotion != null && !promotion.isEmpty())
+			output.append(" ").append(promotion);
+		return output.toString();
+	}
+}

리뷰: 이런 출력 데이터 처리 부분은 뷰에서 진행하거나 DTO를 만들어서 하는 게  MVC 패턴에 맞는 거라고 생각하는데 어떻게 생각하시나요??
@@ -0,0 +1,28 @@
+package store.model.domain.product;
+
+import java.util.List;
+
+public class Promotions {
+	private final List<Promotion> promotions;
+
+	public Promotions(List<Promotion> promotions) {
+		this.promotions = promotions;
+	}
+
+	public Promotion findPromotionByName(String promotionName) {
+		return promotions.stream()
+			.filter(promotion -> promotion.getName().equals(promotionName))
+			.findFirst()
+			.orElse(null);
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		for (Promotion promotion : promotions) {
+			sb.append(promotion.toString()).append("\n");
+		}
+		return sb.toString();
+	}
+}
+

리뷰: promotion.getName.equals 부분을 Promotion 내부 메서드로 만들어서 boolean 값을 반환하게 하면 캡슐화를 생각할 수 있을 것 같아요
@@ -0,0 +1,72 @@
+package store.controller;
+
+import static store.constant.AnswerConstant.ANSWER_YES;
+
+import java.util.Map;
+
+import store.constant.file.FilePath;
+import store.model.domain.customer.Customer;
+import store.model.domain.product.Products;
+import store.model.domain.product.Promotions;
+import store.service.CalculateService;
+import store.service.FileService;
+import store.util.InventoryManager;
+import store.util.PromotionHandler;
+import store.util.ReceiptGenerator;
+import store.view.InputView;
+import store.view.OutputView;
+
+public class StoreController {
+	private final FileService fileService;
+	private final CalculateService calculateService;
+
+	public StoreController(FileService fileService, CalculateService calculateService) {
+		this.fileService = fileService;
+		this.calculateService = calculateService;
+	}
+
+	public void start() {
+		try {
+			boolean isProceed = true;
+			Products products = fileService.createProducts(FilePath.PRODUCT_FILE_PATH.getFilePath());
+			Promotions promotions = fileService.createPromotions(FilePath.PROMOTION_FILE_PATH.getFilePath());
+
+			while (isProceed) {
+				processBuying(products, promotions);
+				isProceed = shouldProceed();
+				OutputView.printNewLine();
+			}
+		} catch (IllegalArgumentException e) {
+			OutputView.printError(e.getMessage());
+			start();
+		}
+	}
+
+	private void processBuying(Products products, Promotions promotions) {
+		OutputView.promptCurrentStock(products);
+		Customer customer = getCustomerBuyProduct(products);
+
+		PromotionHandler.handlePromotionMessages(customer, products, promotions);
+		InventoryManager.decreaseProductQuantities(customer, products);
+		ReceiptGenerator.generateReceipt(customer, products, promotions, calculateService);
+	}
+
+	private boolean shouldProceed() {
+		OutputView.promptContinueMessage();
+		String checkProceed = InputView.checkProceedPurchase();
+		return checkProceed.equals(ANSWER_YES);
+	}
+
+	private Customer getCustomerBuyProduct(Products products) {
+		while (true) {
+			try {
+				OutputView.promptBuyProductMessage();
+				Map<String, Integer> buyingProduct = InputView.buyProduct();
+				return new Customer(buyingProduct, products);
+			} catch (IllegalArgumentException e) {
+				OutputView.printError(e.getMessage());
+			}
+		}
+	}
+}
+

리뷰: 그냥 궁금한건데 이거 탭 8칸인건가요???
@@ -0,0 +1,82 @@
+package store.util;
+
+import static store.constant.message.ErrorMessage.*;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import store.model.domain.product.Product;
+import store.model.domain.product.Promotion;
+
+public class Parser {
+	public static List<Product> parseProduct(List<String> lines) {
+		return lines.stream()
+			.skip(1)
+			.map(Parser::parseProductLine)
+			.collect(Collectors.toList());
+	}
+
+	public static List<Promotion> parsePromotion(List<String> lines) {
+		return lines.stream()
+			.skip(1)
+			.map(Parser::parsePromotionLine)
+			.collect(Collectors.toList());
+	}
+
+	private static Product parseProductLine(String line) {
+		List<String> columns = parseLine(line);
+		String name = columns.get(0);
+		int price = Integer.parseInt(columns.get(1));
+		int quantity = Integer.parseInt(columns.get(2));
+
+		String promotion = "";
+		if (!"null".equals(columns.get(3))) {
+			promotion = columns.get(3);
+		}
+		return new Product(name, price, quantity, promotion);
+	}
+
+	private static Promotion parsePromotionLine(String line) {
+		List<String> columns = parseLine(line);
+		String name = columns.get(0);
+		int buy = Integer.parseInt(columns.get(1));
+		int get = Integer.parseInt(columns.get(2));
+		LocalDate startDate = LocalDate.parse(columns.get(3));
+		LocalDate endDate = LocalDate.parse(columns.get(4));
+
+		return new Promotion(name, buy, get, startDate, endDate);
+	}
+
+	private static List<String> parseLine(String line) {
+		return Stream.of(line.split(","))
+			.map(String::trim)
+			.collect(Collectors.toList());
+	}
+
+	public static Map<String, Integer> parseBuyProduct(String buyingProduct) {
+		return Arrays.stream(buyingProduct.split("\\],\\["))
+			.map(product -> product.replaceAll("[\\[\\]]", ""))
+			.map(product -> {
+				String[] details = product.split("-");
+				validateInput(details);
+				return details;
+			})
+			.collect(Collectors.toMap(
+				details -> details[0],
+				details -> Integer.parseInt(details[1]),
+				(existing, replacement) -> existing,
+				LinkedHashMap::new
+			));
+	}
+
+	private static void validateInput(String[] details) {
+		if (details.length != 2 || !details[1].matches("\\d+")) {
+			throw new IllegalArgumentException(INVALID_FORMAT.getMessage());
+		}
+	}
+}

리뷰: 하나의 메서드에서 너무 많은 역할을 하고 있다고 생각해요.
map으로 계속 이어서 작업을 하고 계신데 메서드 분리를 하시는 편이 좋았을 것 같아요..!
@@ -0,0 +1,82 @@
+package store.util;
+
+import static store.constant.message.ErrorMessage.*;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import store.model.domain.product.Product;
+import store.model.domain.product.Promotion;
+
+public class Parser {
+	public static List<Product> parseProduct(List<String> lines) {
+		return lines.stream()
+			.skip(1)
+			.map(Parser::parseProductLine)
+			.collect(Collectors.toList());
+	}
+
+	public static List<Promotion> parsePromotion(List<String> lines) {
+		return lines.stream()
+			.skip(1)
+			.map(Parser::parsePromotionLine)
+			.collect(Collectors.toList());
+	}
+
+	private static Product parseProductLine(String line) {
+		List<String> columns = parseLine(line);
+		String name = columns.get(0);
+		int price = Integer.parseInt(columns.get(1));
+		int quantity = Integer.parseInt(columns.get(2));
+
+		String promotion = "";
+		if (!"null".equals(columns.get(3))) {
+			promotion = columns.get(3);
+		}
+		return new Product(name, price, quantity, promotion);
+	}
+
+	private static Promotion parsePromotionLine(String line) {
+		List<String> columns = parseLine(line);
+		String name = columns.get(0);
+		int buy = Integer.parseInt(columns.get(1));
+		int get = Integer.parseInt(columns.get(2));
+		LocalDate startDate = LocalDate.parse(columns.get(3));
+		LocalDate endDate = LocalDate.parse(columns.get(4));
+
+		return new Promotion(name, buy, get, startDate, endDate);
+	}
+
+	private static List<String> parseLine(String line) {
+		return Stream.of(line.split(","))
+			.map(String::trim)
+			.collect(Collectors.toList());
+	}
+
+	public static Map<String, Integer> parseBuyProduct(String buyingProduct) {
+		return Arrays.stream(buyingProduct.split("\\],\\["))
+			.map(product -> product.replaceAll("[\\[\\]]", ""))
+			.map(product -> {
+				String[] details = product.split("-");
+				validateInput(details);
+				return details;
+			})
+			.collect(Collectors.toMap(
+				details -> details[0],
+				details -> Integer.parseInt(details[1]),
+				(existing, replacement) -> existing,
+				LinkedHashMap::new
+			));
+	}
+
+	private static void validateInput(String[] details) {
+		if (details.length != 2 || !details[1].matches("\\d+")) {
+			throw new IllegalArgumentException(INVALID_FORMAT.getMessage());
+		}
+	}
+}

리뷰: 만약 데이터가 "[콜라-3],[에너지바-5]"가 아닌 "콜라-3],[에너지바" 이런식으로 들어오면(누가 그렇게 하겠냐만은) 타입 에러가 나지 않을 것 같은데 이런 부분에 대한 검증도 사소하지만 있으면 좋을 것 같아요
@@ -0,0 +1,66 @@
+package store.model.domain.customer;
+
+import static store.constant.message.ErrorMessage.INVALID_FORMAT;
+import static store.constant.message.ErrorMessage.NON_EXIST_PRODUCT;
+import static store.constant.message.ErrorMessage.OVER_STOCK_QUANTITY;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import store.model.domain.product.Products;
+
+public class Customer {
+	private final Map<String, Integer> buyingProduct = new HashMap<>();
+	private final Map<String, Integer> giftProduct = new HashMap<>();
+
+	public Customer(Map<String, Integer> initialBuyingProduct, Products products) {
+		validateBuyingProduct(initialBuyingProduct, products);
+	}
+
+	private void validateBuyingProduct(Map<String, Integer> inputProduct, Products products) {
+		inputProduct.forEach((productName, quantity) -> {
+			validateProduct(quantity, productName, products);
+			buyingProduct.put(productName, quantity);
+		});
+	}
+
+	private void validateProduct(Integer quantity, String productName, Products products) {
+		validateProductFormat(quantity);
+		validateProductExist(productName, products);
+		validateStockQuantity(productName, quantity, products);
+	}
+
+	private void validateProductFormat(Integer quantity) {
+		if (quantity == null || quantity <= 0) {
+			throw new IllegalArgumentException(INVALID_FORMAT.getMessage());
+		}
+	}
+
+	private void validateProductExist(String productName, Products products) {
+		if (!products.isProductContain(productName)) {
+			throw new IllegalArgumentException(NON_EXIST_PRODUCT.getMessage());
+		}
+	}
+
+	private void validateStockQuantity(String productName, Integer quantity, Products products) {
+		if (products.getTotalStockByName(productName) < quantity) {
+			throw new IllegalArgumentException(OVER_STOCK_QUANTITY.getMessage());
+		}
+	}
+
+	public Map<String, Integer> getBuyingProduct() {
+		return new HashMap<>(buyingProduct);
+	}
+
+	public Map<String, Integer> getGiftProduct() {
+		return new HashMap<>(giftProduct);
+	}
+
+	public void addAdditionalProduct(String productName, int additionalQuantity) {
+		buyingProduct.merge(productName, additionalQuantity, Integer::sum);
+	}
+
+	public void addGiftProduct(String productName, int giftQuantity) {
+		giftProduct.merge(productName, giftQuantity, Integer::sum);
+	}
+}

리뷰: private 메서드는 위의 public 메서드와 관련되어도 하단에 위치하는 게 좋다는 피드백이 있었어요! 저도 찾아가기가 public -> 관련된 private 메서드 순이 훨씬 편하기는한데 그 공통 피드백을 보고 난 후에 왜인지 알아보고 생각을 좀 해봤었습니다!
@@ -0,0 +1,60 @@
+package store.model.domain.product;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Products {
+	private final List<Product> products;
+
+	public Products(List<Product> products) {
+		this.products = products;
+	}
+
+	public int getProductPrice(String productName) {
+		return products.stream()
+			.filter(product -> product.getName().equals(productName))
+			.findFirst()
+			.map(Product::getPrice)
+			.orElse(0);
+	}
+
+	public int getTotalStockByName(String productName) {
+		return products.stream()
+			.filter(product -> product.getName().equals(productName))
+			.mapToInt(Product::getQuantity)
+			.sum();
+	}
+
+	public Product findProductByName(String productName) {
+		return products.stream()
+			.filter(product -> product.getName().equals(productName))
+			.findFirst()
+			.orElse(null);
+	}
+
+	public boolean isProductContain(String productName) {
+		return products.stream()
+			.anyMatch(product -> product.getName().equals(productName));
+	}
+
+	public Product findPromotionProductByName(String productName) {
+		return products.stream()
+			.filter(product -> product.getName().equals(productName) && product.getPromotion() != null)
+			.findFirst()
+			.orElse(null);
+	}
+
+	public Product findRegularProductByName(String productName) {
+		return products.stream()
+			.filter(product -> product.getName().equals(productName) && product.getPromotion() == null)
+			.findFirst()
+			.orElse(null);
+	}
+
+	@Override
+	public String toString() {
+		return products.stream()
+			.map(Product::toString)
+			.collect(Collectors.joining("\n"));
+	}
+}

리뷰: product.getName().equals 부분이 여기 많으신데 직접 Product를 사용하는 것보다 Product내에 메서드를 만들어서 동일한지 찾게 하는 메서드를 하나 만드는게 객체간 협력과 객체지향적인것 같다고 생각합니다!
@@ -0,0 +1,66 @@
+package store.model.domain.customer;
+
+import static store.constant.message.ErrorMessage.INVALID_FORMAT;
+import static store.constant.message.ErrorMessage.NON_EXIST_PRODUCT;
+import static store.constant.message.ErrorMessage.OVER_STOCK_QUANTITY;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import store.model.domain.product.Products;
+
+public class Customer {
+	private final Map<String, Integer> buyingProduct = new HashMap<>();
+	private final Map<String, Integer> giftProduct = new HashMap<>();
+
+	public Customer(Map<String, Integer> initialBuyingProduct, Products products) {
+		validateBuyingProduct(initialBuyingProduct, products);
+	}
+
+	private void validateBuyingProduct(Map<String, Integer> inputProduct, Products products) {
+		inputProduct.forEach((productName, quantity) -> {
+			validateProduct(quantity, productName, products);
+			buyingProduct.put(productName, quantity);
+		});
+	}
+
+	private void validateProduct(Integer quantity, String productName, Products products) {
+		validateProductFormat(quantity);
+		validateProductExist(productName, products);
+		validateStockQuantity(productName, quantity, products);
+	}
+
+	private void validateProductFormat(Integer quantity) {
+		if (quantity == null || quantity <= 0) {
+			throw new IllegalArgumentException(INVALID_FORMAT.getMessage());
+		}
+	}
+
+	private void validateProductExist(String productName, Products products) {
+		if (!products.isProductContain(productName)) {
+			throw new IllegalArgumentException(NON_EXIST_PRODUCT.getMessage());
+		}
+	}
+
+	private void validateStockQuantity(String productName, Integer quantity, Products products) {
+		if (products.getTotalStockByName(productName) < quantity) {
+			throw new IllegalArgumentException(OVER_STOCK_QUANTITY.getMessage());
+		}
+	}
+
+	public Map<String, Integer> getBuyingProduct() {
+		return new HashMap<>(buyingProduct);
+	}
+
+	public Map<String, Integer> getGiftProduct() {
+		return new HashMap<>(giftProduct);
+	}
+
+	public void addAdditionalProduct(String productName, int additionalQuantity) {
+		buyingProduct.merge(productName, additionalQuantity, Integer::sum);
+	}
+
+	public void addGiftProduct(String productName, int giftQuantity) {
+		giftProduct.merge(productName, giftQuantity, Integer::sum);
+	}
+}

리뷰: HashMap을 쓰시면 순서가 보장 안될텐데 주문한 순대로 있을 수 있게 순서가 보장되는 다른 자료구조를 쓰셨어도 좋을 것 같아요! 그리고 String말고 그냥 Product를 넣어줬으면 Products에서 매번 상품을 찾을 필요가 없을 것 같은데 이렇게 하신 이유가 있으실까요??
@@ -0,0 +1,52 @@
+package store.model.domain.product;
+
+public class Product {
+	private final String name;
+	private final int price;
+	private int quantity;
+	private final String promotion;
+
+	public Product(String name, int price, int quantity, String promotion) {
+		this.name = name;
+		this.price = price;
+		this.quantity = quantity;
+		this.promotion = promotion;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public int getPrice() {
+		return price;
+	}
+
+	public int getQuantity() {
+		return quantity;
+	}
+
+	public String getPromotion() {
+		return promotion;
+	}
+
+	public void decreaseQuantity(int buyQuantity) {
+		if (buyQuantity <= quantity) {
+			quantity -= buyQuantity;
+		}
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder output = new StringBuilder(String.format("- %s %,d원", name, price));
+
+		if (quantity == 0) {
+			output.append(" 재고 없음");
+		}
+		if (quantity != 0) {
+			output.append(String.format(" %d개", quantity));
+		}
+		if (promotion != null && !promotion.isEmpty())
+			output.append(" ").append(promotion);
+		return output.toString();
+	}
+}

리뷰: 저는 Product이 Promotion을 가지고 있을 수 있게 해놨는데 혹시 promotino을 String으로만 갖게 하신 이유가 있으실까요?? Promotionhanlder에서 Products와 Promotions을 함께 받으면서 찾으시는 거 보고 궁금해서 물어봅니다!