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

[자판기] 한재모 미션 제출합니다. #179

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
cbbb052
docs(README): 기능목록 초안 작성
Hanjaemo Nov 22, 2023
108910d
feat(RandomNumberGenerator): 랜덤 숫자 생성 기능 구현
Hanjaemo Nov 22, 2023
bc0287e
feat(VendingMachine): 상품 추가 기능 구현
Hanjaemo Nov 22, 2023
fcd832c
feat(Product): 상품 수량 1개 감소 기능 구현
Hanjaemo Nov 22, 2023
aaa4f31
feat(InputAmount): 투입 금액 상품 가격만큼 감소 기능 구현
Hanjaemo Nov 22, 2023
e7fc6e7
feat(InputView): 자판기가 보유하고 있는 금액 입력 기능 구현
Hanjaemo Nov 22, 2023
2538f2b
feat(InputView): 자판기에 추가할 상품 입력 기능 구현
Hanjaemo Nov 22, 2023
fa3769c
feat(InputView): 투입 금액 입력 기능 구현
Hanjaemo Nov 22, 2023
b02888a
feat(InputView): 구매할 상품명 입력 기능 구현
Hanjaemo Nov 22, 2023
c831690
feat(OutputView): 자판기가 보유한 동전 출력 기능 구현
Hanjaemo Nov 22, 2023
32d9097
feat(OutputView): 남은 투입 금액 출력 기능 구현
Hanjaemo Nov 22, 2023
473978a
feat(OutputView): 잔돈 출력 기능 구현
Hanjaemo Nov 22, 2023
8c98537
docs(README): 기능 구체화
Hanjaemo Nov 22, 2023
4564904
feat(VendingMachine, Product): 남은 투입 금액이 상품의 최저 가격보다 적은지 확인하는 기능 구현
Hanjaemo Nov 22, 2023
9aa7b95
feat(VendingMachineTest): 남은 투입 금액이 상품의 최저 가격보다 적은지 확인하는 기능 단위 테스트
Hanjaemo Nov 22, 2023
a031dd7
feat(VendingMachine, Product): 모든 상품이 소진되었는지 확인하는 기능 구현
Hanjaemo Nov 22, 2023
25da272
feat(VendingMachineTest): 모든 상품이 소진되었는지 확인하는 기능 단위 테스트
Hanjaemo Nov 22, 2023
026a5d0
chore: domain 패키지 생성
Hanjaemo Nov 22, 2023
60f36e2
feat(Coin, VendingMachine, RandomNumberGenerator): 동전 생성 기능 구현
Hanjaemo Nov 22, 2023
673a842
feat(Controller, VendingMachine, OutputView): 자판기 동전 생성 및 출력 기능 구현
Hanjaemo Nov 22, 2023
60abfee
feat(Controller, InputView): 상품 추가 기능 구현
Hanjaemo Nov 22, 2023
46cb1b4
feat: 상품 구매 기능 구현
Hanjaemo Nov 22, 2023
6dac158
feat: 잔돈 반환 기능 구현
Hanjaemo Nov 22, 2023
04ce38c
docs(README): 기능 목록 최신화
Hanjaemo Nov 22, 2023
cb525af
feat(OutputView): 에러 메시지 출력 기능 구현
Hanjaemo Nov 22, 2023
a8e3879
feat(Product, Controller): 상품 가격 검증 기능 구현
Hanjaemo Nov 22, 2023
9629730
feat(VendingMachineAmount, Controller): 자판기 보유 금액 검증 기능 구현
Hanjaemo Nov 22, 2023
f7a19ba
docs(README): 기능 목록 미션 제목 수정
Hanjaemo Nov 22, 2023
506f681
refactor(VendingMachine): 메서드 분리
Hanjaemo Nov 22, 2023
ded3b44
feat(InputAmount, Controller): 투입 금액 검증 기능 구현
Hanjaemo Nov 22, 2023
6bcefd6
refactor(Controller): 반복 조건문의 의도가 명확하도록 수정
Hanjaemo Nov 22, 2023
b4d384b
refactor(Converter, Controller, InputView): Converter 클래스를 사용하여 정수로 변…
Hanjaemo Nov 22, 2023
d73a539
chore(RandomNumberGenerator, VendingMachine): RandomNumberGenerator 클…
Hanjaemo Nov 22, 2023
64bf973
refactor(Constants, InputAmount): 매직 리터럴 상수화 처리
Hanjaemo Nov 22, 2023
ddc1f52
refactor(Product): 매직 넘버 상수화
Hanjaemo Nov 22, 2023
5e5171e
refactor(VendingMachine, VendingMachineAmount, Controller): 캡슐화를 통해 유…
Hanjaemo Nov 22, 2023
5a7ac57
refactor(VendingMachineAmount): 상수화 및 불필요한 메서드 제거
Hanjaemo Nov 22, 2023
6aca1da
refactor: 에러 메시지 수정
Hanjaemo Nov 22, 2023
169b66b
test(InputAmountTest): 검증 기능 단위 테스트
Hanjaemo Nov 22, 2023
d42a7b4
test(ProductTest): 상품 가격 검증 기능 예외 처리
Hanjaemo Nov 22, 2023
7fad681
style: 불필요한 import문 제거
Hanjaemo Nov 22, 2023
45ec4bc
docs(README): 기능 목록 최신화
Hanjaemo Nov 22, 2023
6059b55
feat(InputView, Controller): 상품 입력 형식 검증 기능 구현
Hanjaemo Nov 22, 2023
28e0841
feat(Controller): 예외 처리
Hanjaemo Nov 22, 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
30 changes: 30 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 자판기

## 기능
- [x] 입력 받은 금액에 따라 동전을 무작위로 생성한다.
- [x] 입력 받은 상품 하나를 추가한다.
- [x] 구입한 상품의 수량을 1 감소시킨다.
- [x] 상품 가격만큼 투입 금액을 감소시킨다.
- [x] 남은 투입 금액이 상품의 최저 가격보다 적은지 확인한다.
- [x] 모든 상품이 소진되었는지 확인한다.
- [x] 잔돈을 반환한다.
- 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환한다.
- 반환되지 않은 금액은 자판기에 남는다.

## 입력
- [x] 자판기가 보유한 금액을 입력 받는다.
- [x] 자판기에 추가할 상품명, 가격, 수량을 입력 받는다.
- 상품명, 가격, 수량은 쉼표로, 개별 상품은 대괄호([])로 묶어 세미콜론(;)으로 구분한다.
- [x] 투입 금액을 입력 받는다.
- [x] 구매할 상품의 상품명을 입력 받는다.

## 출력
- [x] 자판기가 보유한 동전 내역을 출력한다.
- [x] 남은 투입 금액을 출력한다.
- [x] 잔돈을 출력한다.

## 검증 내용
- [x] 상품 가격은 100원부터 시작하며, 10원으로 나누어떨어져야 한다.
- [x] 자판기 보유 금액은 10원부터 시작하며, 10원으로 나누어떨어져야 한다.
- [x] 투입 금액은 10원부터 시작하며, 10원으로 나누어떨어져야 한다.
- [x] 자판기에 추가할 상품의 상품명, 가격, 수량은 쉼표로, 개별 상품은 대괄호([])로 묶어 세미콜론(;)으로 구분해야 한다.
4 changes: 4 additions & 0 deletions src/main/java/vendingmachine/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package vendingmachine;

import vendingmachine.controller.Controller;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
Controller controller = new Controller();
controller.run();
}
}
16 changes: 0 additions & 16 deletions src/main/java/vendingmachine/Coin.java

This file was deleted.

16 changes: 16 additions & 0 deletions src/main/java/vendingmachine/constants/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package vendingmachine.constants;

public enum Constants {

ERROR_PREFIX("[ERROR]");

private final String value;

Constants(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
76 changes: 76 additions & 0 deletions src/main/java/vendingmachine/controller/Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package vendingmachine.controller;

import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import vendingmachine.domain.Coin;
import vendingmachine.domain.InputAmount;
import vendingmachine.domain.Product;
import vendingmachine.domain.VendingMachine;
import vendingmachine.domain.VendingMachineAmount;
import vendingmachine.utils.Converter;
import vendingmachine.view.InputView;
import vendingmachine.view.OutputView;

public class Controller {

public void run() {
VendingMachine vendingMachine = new VendingMachine();
VendingMachineAmount vendingMachineAmount = repeatReadForInvalid(this::createVendingMachineAmount);
addCoinsToVendingMachine(vendingMachineAmount, vendingMachine);
addProductsToVendingMachine(vendingMachine);

InputAmount inputAmount = repeatReadForInvalid(this::createInputAmount);
repeatPurchaseProduct(vendingMachine, inputAmount);

Map<Coin, Integer> changes = vendingMachine.changes(inputAmount);
OutputView.printChanges(changes);
}

private void addCoinsToVendingMachine(VendingMachineAmount vendingMachineAmount, VendingMachine vendingMachine) {
vendingMachine.addCoins(vendingMachineAmount);
OutputView.printCoinsOfVendingMachine(vendingMachine.getCoins());
}

private void addProductsToVendingMachine(VendingMachine vendingMachine) {
List<String> inputProducts = repeatReadForInvalid(InputView::readProducts);
for (String inputProduct : inputProducts) {
String[] split = inputProduct.split(",");
vendingMachine.addProduct(repeatReadForInvalid(() -> this.createProduct(split)));
}
}

private InputAmount createInputAmount() {
return new InputAmount(InputView.readInputAmount());
}

private void repeatPurchaseProduct(VendingMachine vendingMachine, InputAmount inputAmount) {
while (canPurchase(vendingMachine, inputAmount)) {
OutputView.printRemainingInputAmount(inputAmount.getAmount());
String productNameToPurchase = repeatReadForInvalid(InputView::readProductNameToPurchase);
vendingMachine.purchase(productNameToPurchase, inputAmount);
}
}

private boolean canPurchase(VendingMachine vendingMachine, InputAmount inputAmount) {
return !vendingMachine.isAllPriceGreaterThan(inputAmount.getAmount())
&& !vendingMachine.isAllProductSoldOut();
}

private Product createProduct(String[] split) {
return new Product(split[0], Converter.convertToInt(split[1]), Converter.convertToInt(split[2]));
}

private VendingMachineAmount createVendingMachineAmount() {
return new VendingMachineAmount(InputView.readVendingMachineAmount());
}

private <T> T repeatReadForInvalid(Supplier<T> reader) {
try {
return reader.get();
} catch (IllegalArgumentException e) {
OutputView.printErrorMessage(e);
return repeatReadForInvalid(reader);
}
}
}
35 changes: 35 additions & 0 deletions src/main/java/vendingmachine/domain/Coin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package vendingmachine.domain;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public enum Coin {
COIN_500(500),
COIN_100(100),
COIN_50(50),
COIN_10(10);

private final int amount;

Coin(final int amount) {
this.amount = amount;
}

public static List<Integer> getCoinAmounts() {
return Arrays.stream(Coin.values())
.map(coin -> coin.amount)
.collect(Collectors.toList());
}

public static Coin from(int amount) {
return Arrays.stream(Coin.values())
.filter(coin -> coin.amount == amount)
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}

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

import vendingmachine.constants.Constants;

public class InputAmount {

private static final int MIN_INPUT_AMOUNT = 10;
private static final int INPUT_AMOUNT_UNIT = 10;

private int amount;

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

private void validate(int amount) {
if (amount < MIN_INPUT_AMOUNT) {
throw new IllegalArgumentException(
String.format("%s 투입 금액은 %d원 이상이어야 합니다.",
Constants.ERROR_PREFIX.getValue(), MIN_INPUT_AMOUNT));
}
if (amount % INPUT_AMOUNT_UNIT != 0) {
throw new IllegalArgumentException(
String.format("%s 투입 금액은 %d원 단위만 가능합니다.",
Constants.ERROR_PREFIX.getValue(), INPUT_AMOUNT_UNIT));
}
}

public void decrease(int amount) {
this.amount -= amount;
}

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

import vendingmachine.constants.Constants;

public class Product {

private static final int MIN_PRICE = 100;
private static final int PRICE_UNIT = 10;

private String name;
private int price;
private int quantity;

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

private void validatePrice(int price) {
if (price < MIN_PRICE) {
throw new IllegalArgumentException(
String.format("%s 상품 가격은 %d원 이상이어야 합니다.",
Constants.ERROR_PREFIX.getValue(), MIN_PRICE));
}
if (price % PRICE_UNIT != 0) {
throw new IllegalArgumentException(
String.format("%s 상품 가격은 %d원 단위만 가능합니다.",
Constants.ERROR_PREFIX.getValue(), PRICE_UNIT));
}
}

public void decreaseQuantity() {
this.quantity--;
}

public boolean isPriceGreaterThan(int amount) {
return price > amount;
}

public boolean isSoldOut() {
return quantity == 0;
}

public boolean isName(String name) {
return this.name.equals(name);
}

public int getPrice() {
return price;
}
}
79 changes: 79 additions & 0 deletions src/main/java/vendingmachine/domain/VendingMachine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package vendingmachine.domain;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import vendingmachine.utils.RandomNumberGenerator;

public class VendingMachine {

private Map<Coin, Integer> coins = new EnumMap<>(Coin.class);
private List<Product> products = new ArrayList<>();

public VendingMachine() {
coins.put(Coin.COIN_500, 0);
coins.put(Coin.COIN_100, 0);
coins.put(Coin.COIN_50, 0);
coins.put(Coin.COIN_10, 0);
}

public void addCoins(VendingMachineAmount vendingMachineAmount) {
while (vendingMachineAmount.isGreaterThanZero()) {
int generatedNumber = RandomNumberGenerator.generate(Coin.getCoinAmounts());
if (vendingMachineAmount.isLessThan(generatedNumber)) {
continue;
}
Coin coin = Coin.from(generatedNumber);
coins.put(coin, coins.get(coin) + 1);
vendingMachineAmount.decrease(generatedNumber);
}
}

public void addProduct(Product product) {
products.add(product);
}

public boolean isAllPriceGreaterThan(int amount) {
return products.stream()
.allMatch(product -> product.isPriceGreaterThan(amount));
}

public boolean isAllProductSoldOut() {
return products.stream()
.allMatch(Product::isSoldOut);
}

public void purchase(String productName, InputAmount inputAmount) {
Product productForPurchase = products.stream()
.filter(product -> product.isName(productName))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("[ERROR] 존재하지 않는 상품입니다."));
productForPurchase.decreaseQuantity();
inputAmount.decrease(productForPurchase.getPrice());
}

public Map<Coin, Integer> changes(InputAmount inputAmount) {
Map<Coin, Integer> changes = new EnumMap<>(Coin.class);
for (Coin coin : Coin.values()) {
int count = coins.get(coin);
repeatChangesForCount(count, inputAmount, changes, coin);
}
return changes;
}

private void repeatChangesForCount(int count, InputAmount inputAmount, Map<Coin, Integer> changes, Coin coin) {
for (int i = 0; i < count; i++) {
if (inputAmount.getAmount() <= 0) {
return;
}
inputAmount.decrease(coin.getAmount());
coins.replace(coin, coins.get(coin) - 1);
changes.put(coin, changes.getOrDefault(coin, 0) + 1);
}
}

public Map<Coin, Integer> getCoins() {
return coins;
}
}
Loading