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

[자판기] 박준수 미션 제출합니다. #2

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
094c88b
docs: 자판기 기능목록/에러처리 정의
GaBaljaintheroom Sep 24, 2023
68759f0
feat: InputView inputMachineAmountHeld 작성
GaBaljaintheroom Sep 24, 2023
25428b4
feat: InputValidator validateMachineAmountHeld 작성
GaBaljaintheroom Sep 24, 2023
95739ed
feat: OutputView 생성
GaBaljaintheroom Sep 24, 2023
9bf6d9a
feat: ProgressMessage 생성
GaBaljaintheroom Sep 24, 2023
4797d65
feat: ErrorMessage 생성
GaBaljaintheroom Sep 24, 2023
5d37b28
feat: Coin coinPrices 작성
GaBaljaintheroom Sep 24, 2023
9ae386a
feat: MakeRandomCoin 생성
GaBaljaintheroom Sep 24, 2023
ab34880
feat: ScreenElement 생성
GaBaljaintheroom Sep 24, 2023
2ba448e
feat: VendingMachine 생성
GaBaljaintheroom Sep 24, 2023
f8c582a
feat: InputView inputProducts 작성
GaBaljaintheroom Sep 24, 2023
fb9b118
feat: Product 생성
GaBaljaintheroom Sep 24, 2023
6189a9e
feat: Products 생성
GaBaljaintheroom Sep 24, 2023
7ec2712
feat: ProgressMessage 메시지 추가작성
GaBaljaintheroom Sep 24, 2023
610a975
feat: ErrorMessage 메시지 추가작성
GaBaljaintheroom Sep 24, 2023
5de2c0c
feat: InputValidator validateDivisionProducts작성
GaBaljaintheroom Sep 24, 2023
0370da1
feat: OutputView 추가 작성
GaBaljaintheroom Sep 24, 2023
55752d5
feat: MachineController 생성
GaBaljaintheroom Sep 24, 2023
0742b1f
feat: InputView inputMoney 작성
GaBaljaintheroom Sep 25, 2023
aac391a
refactor: validateNumeric 수정
GaBaljaintheroom Sep 25, 2023
2737cec
feat: 프로그램 메시지 추가
GaBaljaintheroom Sep 25, 2023
f2a4170
feat: Products validateInputMoney 작성
GaBaljaintheroom Sep 25, 2023
5f61b2c
feat: Calculator 생성
GaBaljaintheroom Sep 25, 2023
5a87642
feat: InputView inputBuyProduct작성
GaBaljaintheroom Sep 25, 2023
be89774
feat: 메시지 추가
GaBaljaintheroom Sep 25, 2023
a5c8eeb
feat: 상품 구매 기능
GaBaljaintheroom Sep 25, 2023
d8bc433
feat: MachineController buyProducts 작성
GaBaljaintheroom Sep 25, 2023
c3c6467
feat: 화면 메시지 추가
GaBaljaintheroom Sep 25, 2023
4484c5f
feat: Calculator returnChange 작성
GaBaljaintheroom Sep 25, 2023
75dff96
feat: VendingMachine returnChange 작성
GaBaljaintheroom Sep 25, 2023
4362d8c
feat: MachineController buyProducts 추가 작성
GaBaljaintheroom Sep 25, 2023
8ef7581
feat: 메인 함수 작성
GaBaljaintheroom Sep 25, 2023
ab62a20
feat: 패키지 구조 변경
GaBaljaintheroom Sep 25, 2023
fdd42ce
docs: 리드미 수정
GaBaljaintheroom Sep 25, 2023
487e2fb
test: CoinTest
GaBaljaintheroom Sep 25, 2023
b2b0a01
test: MakeRandomCoinTest
GaBaljaintheroom Sep 25, 2023
604c9f6
test: ProductTest
GaBaljaintheroom Sep 25, 2023
d032920
test: ProductsTest
GaBaljaintheroom Sep 25, 2023
c80845e
test: VendingMachineTest
GaBaljaintheroom Sep 25, 2023
a631c1f
test: InputValidatorTest
GaBaljaintheroom Sep 25, 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
34 changes: 34 additions & 0 deletions docs/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## 기능 목록

- InputView
- [x] 자판기가 보유하고 있는 금액 입력
- [x] 상품명, 가격, 수량을 입력
- [x] 구매할 상품명 입력

- MakeRandomCoin
- [x] 자판기가 보유하고 있는 금액으로 동전을 무작위로 생성

- product
- [x] 상품 정보 저장
- [x] 상품 저장 형식이 옳지 않으면 오류 발생

- Products
- [x] 상품명, 가격, 수량은 쉼표로, 개별 상품은 대괄호([])로 묶어 세미콜론(;)으로 구분한다.
- [x] 상품 가격은 100원부터 시작하며, 10원으로 나누어떨어져야 한다.
- [x] 상품 최소 가격이 입력 가격 보다 크면 오류 발생
- [x] 상품리스트에 없는 상품이름을 입력하면 오류 발생

- VendingMachine
- [x] 보유 금액, 상품 저장
- [x] 잔돈 반환

- CalculatorService
- [x] 남은 금액이 상품의 최저 가격보다 적거나, 모든 상품이 소진된 경우 바로 잔돈을 돌려준다.
- [x] 잔돈을 돌려줄 때 현재 보유한 최소 개수의 동전으로 잔돈을 돌려준다.
- [x] 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환한다.
- [x] 반환되지 않은 금액은 자판기에 남는다.

## 에러 처리
- [x] 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 해당 부분부터 다시 입력을 받는다.
- [x] 상품 저장 형식 에러 처리
- [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.MachineController;

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

This file was deleted.

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

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

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> coinPrices() {
return Stream.of(Coin.values())
.map(coin -> coin.amount)
.collect(Collectors.toList());
}

public static Coin getByAmount(Integer coinAmount) {
return Stream.of(Coin.values())
.filter(coin -> coin.amount == coinAmount)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NOT_FOUND_COIN.getMessage()));

}

public int getAmount() {
return amount;
}

@Override
public String toString() {
return String.valueOf(amount);
}
}
23 changes: 23 additions & 0 deletions src/main/java/vendingmachine/constants/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package vendingmachine.constants;

public enum ErrorMessage {
MACHINE_COIN_NUMERIC_ERROR("숫자를 입력해 주세요"),
MIN_MACHINE_AMOUNT_HELD_ERROR("자판기 보유 금액은 최소 100원 부터 시작해주세요"),
NOT_FOUND_COIN("존재 하지 않는 Coin 입니다."),
PRODUCT_REGEX_ERROR("일치하지 않는 상품 형식입니다."),
PRODUCT_PRICE_ERROR("상품 가격은 100원부터 시작하며, 10원으로 나누어떨어져야 합니다."),
INPUT_MONEY_ERROR("입력한 금액이 너무 적습니다."),
INPUT_PRODUCT_NAME_ERROR("존재하지 않는 상품명 입니다.");


private static final String ERROR = "[ERROR] ";
private final String message;

ErrorMessage(String message) {
this.message = message;
}

public String getMessage() {
return ERROR + message;
}
}
22 changes: 22 additions & 0 deletions src/main/java/vendingmachine/constants/ProgressMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package vendingmachine.constants;

public enum ProgressMessage {
INPUT_MACHINE_AMOUNT_HELD("자판기가 보유하고 있는 금액을 입력해 주세요."),
MACHINE_AMOUNT_HELD("자판기가 보유한 동전"),
INPUT_PRODUCTS_DETAIL("상품명과 가격, 수량을 입력해 주세요."),
INPUT_MONEY("투입 금액을 입력해 주세요."),
INPUT_BUY_PRODUCT_NAME("구매할 상품명을 입력해 주세요."),
CUR_INPUT_MONEY("투입 금액: %d원"),
RETURN_CHANGE("잔돈");

private final String message;

ProgressMessage(String message) {
this.message = message;
}

@Override
public String toString() {
return message;
}
}
18 changes: 18 additions & 0 deletions src/main/java/vendingmachine/constants/ScreenElement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package vendingmachine.constants;

public enum ScreenElement {
COIN_UNIT("원"),
DIVISION(" - "),
COUNT_UNIT("개");

private final String value;

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

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

import vendingmachine.domain.Products;
import vendingmachine.domain.VendingMachine;
import vendingmachine.service.CalculatorService;
import vendingmachine.view.InputView;
import vendingmachine.view.OutputView;

public class MachineController {

private final InputView inputView;
private final OutputView outputView;
private final VendingMachine vendingMachine;
private final CalculatorService calculatorService;

public MachineController() {
this.inputView = new InputView();
this.outputView = new OutputView();
this.vendingMachine = new VendingMachine();
this.calculatorService = new CalculatorService(vendingMachine);
}

public void run() {
initVendingMachineAmountHeld();

Products products = inputView.inputProducts();
vendingMachine.registerProducts(products);

calculatorService.receiveMoney(inputView.inputMoney(products));
buyProducts(products);
}

private void initVendingMachineAmountHeld() {
int amountHeld = inputView.inputMachineAmountHeld();
vendingMachine.saveRandomCoin(amountHeld);
outputView.printMachineAmountHeld(vendingMachine.showRandomCoins());
}

private void buyProducts(Products products) {
while (true) {
outputView.printCurInputMoney(calculatorService.getInputMoney());
if (calculatorService.isBuyingProducts()) break;

String name = inputView.inputBuyProduct(products);
calculatorService.buyProducts(name);
}
outputView.printResultChange(calculatorService.returnChange());
}
}
42 changes: 42 additions & 0 deletions src/main/java/vendingmachine/domain/MakeRandomCoin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package vendingmachine.domain;

import camp.nextstep.edu.missionutils.Randoms;
import vendingmachine.constants.Coin;

import java.util.ArrayList;
import java.util.List;

public class MakeRandomCoin {
private final List<Integer> randomCoins = new ArrayList<>();

public void makeCoins(int amount) {
while (isMakingRandomCoins(amount)) {
randomCoins.add(randomCoin());
removeOverCoin(amount);
}
}

private boolean isMakingRandomCoins(int amount) {
return getSum(randomCoins) != amount;
}

private int randomCoin() {
return Randoms.pickNumberInList(Coin.coinPrices());
}

private void removeOverCoin(int amount) {
if (getSum(randomCoins) > amount) {
randomCoins.remove(randomCoins.size() - 1);
}
}

private int getSum(List<Integer> coins) {
return coins.stream()
.mapToInt(Integer::intValue)
.sum();
}

public List<Integer> getRandomCoins() {
return randomCoins;
}
}
41 changes: 41 additions & 0 deletions src/main/java/vendingmachine/domain/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package vendingmachine.domain;

import vendingmachine.constants.ErrorMessage;

public class Product {

private static final int MIN_PRICE = 100;
private static final int COIN_UNIT = 10;
private final String name;
private final int price;
private final int amount;

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

private static boolean isaBreakPriceRule(int price) {
return price < MIN_PRICE || price % COIN_UNIT != 0;
}

private void validatePriceRule(int price) {
if (isaBreakPriceRule(price)) {
throw new IllegalArgumentException(ErrorMessage.PRODUCT_PRICE_ERROR.getMessage());
}
}

public int getPrice() {
return price;
}

public String getName() {
return name;
}

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

import vendingmachine.constants.ErrorMessage;

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

public class Products {

private static final String PRODUCT_DIVISION = ";";
private static final String PRODUCT_DETAIL_DIVISION = ",";
private final List<Product> products;


public Products(String productData) {
products = Arrays.stream(productData.split(PRODUCT_DIVISION))
.map(productToken -> productToken.substring(1, productToken.length() - 1).split(PRODUCT_DETAIL_DIVISION))
.map(productDetails -> new Product(productDetails[0], Integer.parseInt(productDetails[1]), Integer.parseInt(productDetails[2])))
.collect(Collectors.toList());
}

public void validateInputMoney(String money) {
int price = Integer.parseInt(money);
if (getMinProductPrice() > price) {
throw new IllegalArgumentException(ErrorMessage.INPUT_MONEY_ERROR.getMessage());
}
}

public void validateInputProductName(String name) {
if (products.stream().noneMatch(product -> product.getName().equals(name))) {
throw new IllegalArgumentException(ErrorMessage.INPUT_PRODUCT_NAME_ERROR.getMessage());
}
}

public boolean isBuyingProducts(int inputMoney) {
return inputMoney < getMinProductPrice() || isProductSoldOut();
}

private int getMinProductPrice() {
return products.stream()
.mapToInt(Product::getPrice)
.min()
.orElse(Integer.MAX_VALUE);
}

private boolean isProductSoldOut() {
int amount = products.stream()
.mapToInt(Product::getAmount)
.sum();
return amount == 0;
}

public int getProductPrice(String name) {
return products.stream()
.filter(product -> product.getName().equals(name))
.findFirst()
.map(Product::getPrice)
.orElse(Integer.MAX_VALUE);
}
}
Loading