-
Notifications
You must be signed in to change notification settings - Fork 341
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
[자판기] 박진훈 미션 제출합니다 (연습) #169
base: main
Are you sure you want to change the base?
Changes from all commits
33bb09e
8a82769
14fcd46
c72a783
fe08b52
a029d98
02196fd
c2f7005
786b4ca
05ec395
aa7e517
2059c15
9f81ddd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# 자판기 | ||
|
||
## 기능 목록 | ||
|
||
### 자판기 | ||
- [ ] 자판기가 보유하고 있는 금액으로 동전을 무작위로 생성하는 기능 | ||
- [ ] 자판기가 보유하고 있는 금액을 입력받는 기능 | ||
- [ ] 자판기가 보유한 동전을 출력하는 기능 | ||
- [ ] 상품명, 가격, 수량을 입력하여 상품을 추가하는 기능 | ||
- [ ] 상품 가격이 100원 이상인지 확인하는 기능 | ||
- [ ] 상품 가격이 10원으로 나누어지는지 확인하는 기능 | ||
- [ ] 상품명이 다른 상품명과 중복 되지않는지 확인하는 기능 | ||
- [ ] 수량이 양수 인지 확인하는 기능 | ||
- [ ] 남은 금액이 상품의 최저 가격보다 적거나, 모든 상품이 소진된 경우 잔돈 반환 기능 | ||
- [ ] 잔돈을 돌려줄 때 현재 보유한 최소 개수의 동전으로 잔돈 반환 기능 | ||
- [ ] 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환하는 기능 | ||
- [ ] 잔돈 출력 기능 | ||
|
||
### 사용자 | ||
- [ ] 사용자가 투입한 금액으로 상품을 구매하는 기능 | ||
- [ ] 투입 금액이 양수 인지 확인하는 기능 | ||
- [ ] 투입 금액 출력 기능 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
package vendingmachine; | ||
|
||
import vendingmachine.controller.VendingMachineController; | ||
|
||
public class Application { | ||
public static void main(String[] args) { | ||
// TODO: 프로그램 구현 | ||
VendingMachineController vendingMachineController = new VendingMachineController(); | ||
vendingMachineController.runVendingMachine(); | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package vendingmachine.controller; | ||
|
||
import vendingmachine.domain.*; | ||
import vendingmachine.domain.coins.Coins; | ||
import vendingmachine.domain.coins.RandomNumberGenerator; | ||
import vendingmachine.domain.products.Products; | ||
import vendingmachine.view.InputView; | ||
import vendingmachine.view.OutputView; | ||
import java.util.function.Supplier; | ||
|
||
public class VendingMachineController { | ||
|
||
private final InputView inputView = new InputView(); | ||
private final OutputView outputView = new OutputView(); | ||
private VendingMachine vendingMachine; | ||
private Money playerMoney; | ||
|
||
public void runVendingMachine() { | ||
makeVendingMachine(); | ||
operateVendingMachine(); | ||
showResult(); | ||
} | ||
|
||
private String askBuyingProduct() { | ||
outputView.printRemainMoney(playerMoney); | ||
outputView.printInputProductName(); | ||
String name = inputView.readProductName(); | ||
outputView.printNewLine(); | ||
return name; | ||
} | ||
|
||
private void makeVendingMachine() { | ||
Money money = askMachineMoney(); | ||
Coins coins = makeCoins(money); | ||
Products products = askProducts(); | ||
|
||
vendingMachine = VendingMachine.of(coins, products); | ||
} | ||
|
||
private Money askMachineMoney() { | ||
outputView.printInputVendingMachineAmount(); | ||
Money money = reenterProcess(inputView::readMoney); | ||
outputView.printNewLine(); | ||
return money; | ||
} | ||
|
||
private Coins makeCoins(Money money) { | ||
Coins coins = new Coins(); | ||
coins.makeRandomCoins(new RandomNumberGenerator(), money); | ||
outputView.printVendingMachineCoins(coins); | ||
return coins; | ||
} | ||
|
||
private Products askProducts() { | ||
outputView.printInputProducts(); | ||
Products products = reenterProcess(inputView::readProducts); | ||
outputView.printNewLine(); | ||
return products; | ||
} | ||
|
||
private void operateVendingMachine() { | ||
playerMoney = askPlayerMoney(); | ||
while (vendingMachine.isSellProduct(playerMoney)) { | ||
buyProduct(); | ||
} | ||
} | ||
|
||
private Money askPlayerMoney() { | ||
outputView.printInputMoney(); | ||
Money money = reenterProcess(inputView::readMoney); | ||
outputView.printNewLine(); | ||
return money; | ||
} | ||
|
||
private void buyProduct() { | ||
while (true) { | ||
try { | ||
vendingMachine.purchaseProduct(askBuyingProduct(), playerMoney); | ||
return; | ||
} catch (IllegalArgumentException exception) { | ||
outputView.printExceptionMessage(exception); | ||
} | ||
} | ||
} | ||
|
||
private void showResult() { | ||
outputView.printRemainMoney(playerMoney); | ||
outputView.printChanges(vendingMachine.makeChanges(playerMoney)); | ||
} | ||
|
||
private <T> T reenterProcess(Supplier<T> reader) { | ||
while (true) { | ||
try { | ||
return reader.get(); | ||
} catch (IllegalArgumentException exception) { | ||
outputView.printExceptionMessage(exception); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package vendingmachine.domain; | ||
|
||
import vendingmachine.domain.coins.Coin; | ||
import vendingmachine.exception.MoneyRangeException; | ||
|
||
public class Money { | ||
|
||
private static final int MIN_MONEY = 0; | ||
private static final int MAX_MONEY = 1_000_000_000; | ||
private static final int ZERO_MONEY = 0; | ||
|
||
private int money; | ||
|
||
private Money(int money) { | ||
validate(money); | ||
this.money = money; | ||
} | ||
|
||
public static Money from(int money) { | ||
return new Money(money); | ||
} | ||
|
||
private static void validate(int money) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 처음에는 정적 팩토리 메소드 |
||
validateRange(money); | ||
} | ||
|
||
private static void validateRange(int money) { | ||
if (money < MIN_MONEY || money > MAX_MONEY) { | ||
throw new MoneyRangeException(MIN_MONEY, MAX_MONEY); | ||
} | ||
} | ||
|
||
public void useMoney(int pay) { | ||
if (isUseMoney(pay)) { | ||
this.money -= pay; | ||
} | ||
} | ||
|
||
public boolean isUseMoney(int money) { | ||
return (this.money - money) >= ZERO_MONEY; | ||
} | ||
|
||
public int calculateDividingCoin(Coin coin) { | ||
return coin.exchangeCoin(this.money); | ||
} | ||
|
||
public int getMoney() { | ||
return money; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package vendingmachine.domain; | ||
|
||
import vendingmachine.domain.coins.Coins; | ||
import vendingmachine.domain.products.Product; | ||
import vendingmachine.domain.products.Products; | ||
|
||
public class VendingMachine { | ||
|
||
private final Coins coins; | ||
private final Products products; | ||
|
||
private VendingMachine(Coins coins, Products products) { | ||
this.coins = coins; | ||
this.products = products; | ||
} | ||
|
||
public static VendingMachine of(Coins coins, Products products) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 정적 팩토리 메서드 처음봤는데 좋네요..! |
||
return new VendingMachine(coins, products); | ||
} | ||
|
||
public void purchaseProduct(String name, Money money){ | ||
Product product = products.findPurchasableProduct(name, money); | ||
product.purchase(money); | ||
} | ||
|
||
public boolean isSellProduct(Money money) { | ||
return (products.findMinPriceProduct().isPurchaseProduct(money) && !products.isAllQuantityZero()); | ||
} | ||
|
||
public Coins makeChanges(Money money) { | ||
return coins.makeLargestCoins(money); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package vendingmachine.domain.coins; | ||
|
||
import vendingmachine.exception.CoinEmptyException; | ||
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 Coin of(int amount) { | ||
return Arrays.stream(values()) | ||
.filter(coin -> coin.amount == amount) | ||
.findAny() | ||
.orElseThrow(CoinEmptyException::new); | ||
} | ||
Comment on lines
+20
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오..! 이 부분에서 stream을 이용할 생각을 못했었는데 이렇게 사용할수 있겠네요 |
||
|
||
public static List<Integer> makeAmountList() { | ||
return Arrays.stream(values()) | ||
.map(Coin::getAmount) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
public static int findMinAmount() { | ||
return Arrays.stream(values()) | ||
.mapToInt(Coin::getAmount) | ||
.min() | ||
.orElseThrow(CoinEmptyException::new); | ||
} | ||
|
||
public int exchangeCoin(int money) { | ||
return (money / amount); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 괄호로 묶어주지 않아도 될 것 같습니다! |
||
} | ||
|
||
public int multiCoin(int multi) { | ||
return (multi * amount); | ||
} | ||
|
||
public int getAmount() { | ||
return amount; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package vendingmachine.domain.coins; | ||
|
||
import vendingmachine.domain.Money; | ||
|
||
import java.util.Comparator; | ||
import java.util.EnumMap; | ||
import java.util.List; | ||
|
||
public class Coins { | ||
|
||
private static final int INITIAL_COIN_NUMBER = 0; | ||
|
||
private final EnumMap<Coin, Integer> coins; | ||
|
||
public Coins() { | ||
this.coins = initCoins(); | ||
} | ||
|
||
public Coins(EnumMap<Coin, Integer> coins) { | ||
this.coins = coins; | ||
} | ||
|
||
public void makeRandomCoins(NumberGenerator numberGenerator, Money money) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 기능을 더 분리해볼 수 있을 것 같습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 어떤식으로 분리하실지 여쭤봐도될까요?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사실 메서드 라인이 10 라인을 넘어가서 기능 분리를 해보면 어떨까 싶어 제안 드린건데 생각보다 기능 분리가 쉽지 않네요... :( public void makeRandomCoins(NumberGenerator numberGenerator, Money money) {
List<Integer> coinAmounts = Coin.makeAmountList();
while (money.isConvertable()) {
int amount = numberGenerator.generate(coinAmounts);
if (money.isUseMoney(amount)) {
convertToCoin(money, amount);
}
if (!money.isUseMoney(amount)) {
coinAmounts.remove((Integer) amount);
}
}
}
private void convertToCoin(Money money, int amount) {
money.useMoney(amount);
Coin coin = Coin.of(amount);
coins.put(coin, coins.get(coin) + 1);
}
---
public class Money {
public boolean isConvertable() {
return money >= Coin.findMinAmount();
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 너무어렵네요.. 좋은 코드 감사합니다! |
||
List<Integer> coinAmounts = Coin.makeAmountList(); | ||
int minAmount = Coin.findMinAmount(); | ||
|
||
while (money.isUseMoney(minAmount)) { | ||
int number = numberGenerator.generate(coinAmounts); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (money.isUseMoney(number)) { | ||
money.useMoney(number); | ||
this.coins.put(Coin.of(number), this.coins.get(Coin.of(number)) + 1); | ||
} | ||
if (!money.isUseMoney(number)) { | ||
coinAmounts.remove((Integer) number); | ||
} | ||
} | ||
} | ||
|
||
private EnumMap<Coin, Integer> initCoins() { | ||
EnumMap<Coin, Integer> coins = new EnumMap<>(Coin.class); | ||
for (Coin coin : Coin.values()) { | ||
coins.put(coin, INITIAL_COIN_NUMBER); | ||
} | ||
return coins; | ||
} | ||
|
||
public Coins makeLargestCoins(Money money) { | ||
EnumMap<Coin, Integer> changes = new EnumMap<>(Coin.class); | ||
coins.entrySet().stream() | ||
.sorted(Comparator.comparingInt(o -> o.getKey().getAmount())) | ||
.forEach(coin -> changes.put(coin.getKey(), calculateCoin(coin.getKey(), money))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
추가적으로 빈 맵을 선언하시는 게 거슬리시다면 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 변수명 때문에 착각하신거 같아요 ㅠ
|
||
return new Coins(changes); | ||
} | ||
|
||
private int calculateCoin(Coin coin, Money money) { | ||
int count = money.calculateDividingCoin(coin); | ||
int coinNumber = coins.get(coin); | ||
if (coinNumber <= count) { | ||
money.useMoney(coin.multiCoin(coinNumber)); | ||
return coins.get(coin); | ||
} | ||
money.useMoney(coin.multiCoin(count)); | ||
return count; | ||
} | ||
|
||
public EnumMap<Coin, Integer> getCoins() { | ||
return coins; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package vendingmachine.domain.coins; | ||
|
||
import java.util.List; | ||
|
||
public interface NumberGenerator { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. interface 상단에 4주차 미션에서 참고했는데 이런 역할을 하더라구요! |
||
|
||
int generate(List<Integer> numbers); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package vendingmachine.domain.coins; | ||
|
||
import camp.nextstep.edu.missionutils.Randoms; | ||
|
||
import java.util.List; | ||
|
||
|
||
public class RandomNumberGenerator implements NumberGenerator { | ||
|
||
@Override | ||
public int generate(List<Integer> numbers) { | ||
return Randoms.pickNumberInList(numbers); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
개인적으로 while(true)로 작성하면 읽는 도중에 종료조건을 한번 더 신경써야해서
저는 while의 조건구문에 항상 언제 종료된다는것을 표현하고는 합니다!