Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[자판기] 최원준(타칸) 미션 제출합니다! #176

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f2bbdc4
docs : 기능 구현 목록 작성
jhon3242 Nov 20, 2023
9cc5a65
docs : 핵심 기능 한 줄 정리
jhon3242 Nov 20, 2023
701c468
feat : 상품 추가 기능 구현
jhon3242 Nov 20, 2023
f90af6d
feat : 상품 추가 기능 실제로 구현
jhon3242 Nov 20, 2023
5e1b2e6
feat : 상품 리스트 입력 검증 추가
jhon3242 Nov 20, 2023
1517278
feat : 상품 객체에 관한 검증 테스트 추가
jhon3242 Nov 20, 2023
3798a8f
feat : 동전 추가에 대한 기능 구현
jhon3242 Nov 20, 2023
77d3e48
feat : 자판기 보유 동전 출력 구현
jhon3242 Nov 20, 2023
0c825d7
feat : 상품 수량에 대한 검증 추가
jhon3242 Nov 20, 2023
104dbdd
feat : 상품 구매 가능 여부 방식 수정
jhon3242 Nov 20, 2023
aead22c
feat : 잔돈 반환 기능 구현
jhon3242 Nov 20, 2023
7b6c32a
feat : 출력 메시지 수정
jhon3242 Nov 20, 2023
fd2d587
feat : 예외 출력 메시지 수정
jhon3242 Nov 20, 2023
4ae7030
feat : 출력 순서 요구사항에 맞게 수정
jhon3242 Nov 20, 2023
907d026
feat : canBuySomething 테스트 추가
jhon3242 Nov 20, 2023
d74b11a
feat : 상품 구매에 대한 테스트 케이스 추가
jhon3242 Nov 20, 2023
9f41bd3
refactor : 상품 저장소 객체 리팩터링
jhon3242 Nov 20, 2023
9438b01
refactor : CoinStore 객체 생성으로 책임 분리
jhon3242 Nov 20, 2023
db82752
refactor : Money 객체 추가하는 방식으로 리팩터링
jhon3242 Nov 20, 2023
342980d
refactor : 가독성 좋게 메서드 순서 수정
jhon3242 Nov 20, 2023
eec33d5
refactor : 메시지 상수화 리팩터링
jhon3242 Nov 20, 2023
40f93b2
refactor : 요구사항에 맞게 메시지 수정
jhon3242 Nov 20, 2023
4e631d5
fix : 잔돈 반환 이후 동전 개수 및 보유 금액 변화하도록 수정
jhon3242 Nov 20, 2023
567a6ec
refactor : 변하지 않는 인스턴스 변수는 final 추가
jhon3242 Nov 20, 2023
20e116b
refactor : 매직넘버 리팩터링
jhon3242 Nov 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 핵심 기능
- 자판기의 상품 구매를 처리한다.

# 기능 구현 목록
- [X] : 동전 금액을 입력 받는다.
- [X] : `예외 사항` : 빈 값인 경우
- [X] : `예외 사항` : 숫자가 아닌 경우
- [X] : `예외 사항` : 음수인 경우
-
- [X] : 잔돈을 돌려준다.
- [X] : 남은 금액이 최저 가격보다 적은 경우
- [X] : 상품이 모두 소진된 경우
- [X] : 동전으로만 반환한다.
- [X] : 최소 개수의 동전으로 잔돈을 돌려준다.

- [X] : 반환되지 않은 금액은 자판기에 남는다.

- [X] : 상품을 추가할 수 있는 기능이 있다.
- [X] : 상품은 `;` 으로 구분하고 대괄호`[]` 로 묶여 있다.
- [X] : 상품명, 가격, 수량 순서대로 쉼표로 구분되어 입력 받는다.
- [X] : 상품의 가격은 100원 부터 시작하고 10원으로 나누어 떨어진다.

- [X] : 예외 발생시 IllegalArgumentException 발생시키고 다시 입력 받는다.
- [X] : `[ERROR]` 로 시작하는 에러 메시지를 출력한다.

2 changes: 1 addition & 1 deletion src/main/java/vendingmachine/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
VendingMachineController.run();
}
}
31 changes: 30 additions & 1 deletion src/main/java/vendingmachine/Coin.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package vendingmachine;

import camp.nextstep.edu.missionutils.Randoms;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import vendingmachine.message.ExceptionMessage;

public enum Coin {
COIN_500(500),
COIN_100(100),
Expand All @@ -12,5 +18,28 @@ public enum Coin {
this.amount = amount;
}

// 추가 기능 구현
public static Coin getRandomCoin() {
List<Integer> amountList = Arrays.stream(Coin.values())
.map(coin -> coin.amount)
.collect(Collectors.toList());
int randomAmount = Randoms.pickNumberInList(amountList);
return findByAmount(randomAmount);
}

private static Coin findByAmount(int amount) {
return Arrays.stream(Coin.values())
.filter(coin -> coin.amount == amount)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.INVALID_COIN));
}

public static List<Coin> getCoinOrderedList() {
return Arrays.stream(Coin.values())
.sorted((o1, o2) -> o2.amount - o1.amount)
.collect(Collectors.toList());
}

public int getAmount() {
return amount;
}
}
51 changes: 51 additions & 0 deletions src/main/java/vendingmachine/CoinStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package vendingmachine;

import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;

public class CoinStore {
private final Map<Coin, Integer> repository;

public CoinStore(Map<Coin, Integer> repository) {
this.repository = repository;
}

public void addCoinRandomly(Money money) {
while (money.isMoreOrEqualThen(Coin.COIN_10.getAmount())) {
Coin pickedCoin = Coin.getRandomCoin();
if (money.isLessThen(pickedCoin.getAmount())) {
continue;
}
money.minus(pickedCoin.getAmount());
addCoin(pickedCoin);
}
}

private void addCoin(Coin coin) {
repository.put(coin, repository.getOrDefault(coin, 0) + 1);
}

public Map<Coin, Integer> getChange(Money holdingMoney) {
Map<Coin, Integer> change = new EnumMap<>(Coin.class);
Coin.getCoinOrderedList()
.forEach((coin) -> handleChange(change, coin, holdingMoney));
return change;
}

private void handleChange(Map<Coin, Integer> change, Coin coin, Money holdingMoney) {
if (repository.get(coin) == null || repository.get(coin) <= 0) {
return;
}
if (holdingMoney.getAmount() >= coin.getAmount()) {
int quantity = Math.min(holdingMoney.getAmount() / coin.getAmount(), repository.get(coin));
change.put(coin, quantity);
repository.put(coin, repository.get(coin) - quantity);
holdingMoney.minus(coin.getAmount() * quantity);
}
}

public Map<Coin, Integer> getRepository() {
return Collections.unmodifiableMap(repository);
}
}
37 changes: 37 additions & 0 deletions src/main/java/vendingmachine/Money.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package vendingmachine;

import vendingmachine.message.ExceptionMessage;

public class Money {
private int amount;

public Money(int amount) {
validate(amount);
this.amount = amount;
}

private void validate(int amount) {
if (amount < 0) {
throw new IllegalArgumentException(ExceptionMessage.NUMBER_FORMAT);
}
}

public boolean isMoreOrEqualThen(int amount) {
return this.amount >= amount;
}

public boolean isLessThen(int amount) {
return this.amount < amount;
}

public void minus(int amount) {
if (this.amount - amount < 0) {
throw new IllegalArgumentException(ExceptionMessage.LACK_MONEY);
}
this.amount -= amount;
}

public int getAmount() {
return amount;
}
}
43 changes: 43 additions & 0 deletions src/main/java/vendingmachine/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package vendingmachine;

import vendingmachine.message.ExceptionMessage;

public class Product {
public static final int MIN_PRICE = 100;
private final String name;
private final int price;

public Product(String name, int price) {
validate(name, price);
this.name = name;
this.price = price;
}

private void validate(String name, int price) {
validateName(name);
validatePrice(price);
}

private void validateName(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException(ExceptionMessage.BLANK);
}
}

private void validatePrice(int price) {
if (price < MIN_PRICE) {
throw new IllegalArgumentException(ExceptionMessage.INVALID_PRODUCT_PRICE);
}
if (price % 10 != 0) {
throw new IllegalArgumentException(ExceptionMessage.INVALID_PRODUCT_PRICE);
}
}

public int getPrice() {
return price;
}

public String getName() {
return name;
}
}
83 changes: 83 additions & 0 deletions src/main/java/vendingmachine/ProductStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package vendingmachine;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import vendingmachine.message.ExceptionMessage;

public class ProductStore {
private static final String PRODUCT_DELIMITER = ";";
private static final String PRODUCT_REGEX = "^\\[([^,]+),([0-9]+),([0-9]+)\\]$";
private static final Pattern PRODUCT_PATTERN = Pattern.compile(PRODUCT_REGEX);
private static final int NAME_INDEX = 1;
private static final int PRICE_INDEX = 2;
private static final int QUANTITY_INDEX = 3;

private final Map<Product, Integer> repository;

public ProductStore() {
repository = new HashMap<>();
}

public void initProductsByString(String input) {
Arrays.stream(input.split(PRODUCT_DELIMITER))
.forEach(this::handleProductByString);
}

private void handleProductByString(String value) {
Matcher matcher = PRODUCT_PATTERN.matcher(value);
if (matcher.find()) {
String name = matcher.group(NAME_INDEX);
int price = Integer.parseInt(matcher.group(PRICE_INDEX));
int quantity = Integer.parseInt(matcher.group(QUANTITY_INDEX));
Product product = new Product(name, price);
validateQuantity(quantity);
repository.put(product, quantity);
return;
}
throw new IllegalArgumentException(ExceptionMessage.INVALID_PRODUCT_NAME);
}

private void validateQuantity(int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException(ExceptionMessage.LACK_QUANTITY);
}
}

public boolean canBuySomething(int money) {
return getMinPrice() <= money && getLeftTotalProductCount() > 0;
}

private int getMinPrice() {
return repository.keySet()
.stream()
.mapToInt(Product::getPrice)
.min()
.orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.INVALID_PRODUCT_NAME));
}

private int getLeftTotalProductCount() {
return repository.values()
.stream()
.mapToInt(Integer::intValue)
.sum();
}

public Product findProductByName(String name) {
return repository.keySet()
.stream()
.filter((product) -> product.getName().equals(name))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.INVALID_PRODUCT_NAME));
}

public void purchaseProduct(Product product) {
repository.put(product, repository.get(product) - 1);
}

public int getLeftProductCount(Product product) {
return repository.get(product);
}
}
63 changes: 63 additions & 0 deletions src/main/java/vendingmachine/VendingMachine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package vendingmachine;

import java.util.EnumMap;
import java.util.Map;
import vendingmachine.message.ExceptionMessage;

public class VendingMachine {
private final CoinStore coinStore;
private ProductStore productStore;
private Money holdingMoney;

public VendingMachine() {
productStore = new ProductStore();
coinStore = new CoinStore(new EnumMap<>(Coin.class));
holdingMoney = new Money(0);
}

public void initProducts(ProductStore repository) {
this.productStore = repository;
}

public void initHoldingMoney(Money money) {
coinStore.addCoinRandomly(money);
}

public boolean canPurchaseSomething() {
return productStore.canBuySomething(holdingMoney.getAmount());
}

public void initInputMoney(Money inputMoney) {
holdingMoney = inputMoney;
}


public void purchaseProduct(String productName) {
Product product = productStore.findProductByName(productName);
validatePurchase(product);
productStore.purchaseProduct(product);
holdingMoney.minus(product.getPrice());
}

private void validatePurchase(Product product) {
if (holdingMoney.isLessThen(product.getPrice())) {
throw new IllegalArgumentException(ExceptionMessage.LACK_MONEY);
}
if (productStore.getLeftProductCount(product) <= 0) {
throw new IllegalArgumentException(ExceptionMessage.LACK_QUANTITY);
}
}

public Money getHoldingMoney() {
return holdingMoney;
}

public Map<Coin, Integer> getCoinMap() {
return coinStore.getRepository();
}

public Map<Coin, Integer> getChange() {
return coinStore.getChange(holdingMoney);
}
}

Loading