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

[자판기] 박진훈 미션 제출합니다 (연습) #169

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
33bb09e
fix: 자바 버전설정
invalid-email-address Dec 7, 2022
8a82769
docs: 추가 기능 목록
invalid-email-address Dec 7, 2022
14fcd46
feat: 추가 skeleton
invalid-email-address Dec 8, 2022
c72a783
feat: 추가 자판기가 보유하고 있는 금액으로 동전을 무작위로 생성하는 기능
invalid-email-address Dec 8, 2022
fe08b52
test: 추가 자판기가 보유하고 있는 금액으로 동전을 무작위로 생성하는 기능테스트
invalid-email-address Dec 8, 2022
a029d98
feat: 추가 상품명, 가격, 수량을 입력하여 상품을 추가하는 기능
invalid-email-address Dec 9, 2022
02196fd
test: 추가 상품명, 가격, 수량을 입력하여 상품을 추가하는 기능테스트
invalid-email-address Dec 9, 2022
c2f7005
feat: 추가 사용자가 투입한 금액으로 상품을 구매하는 기능
invalid-email-address Dec 9, 2022
786b4ca
test: 추가 사용자가 투입한 금액으로 상품을 구매하는 기능테스트
invalid-email-address Dec 9, 2022
05ec395
feat: 추가 남은 금액이 상품의 최저 가격보다 적거나, 모든 상품이 소진된 경우 잔돈 반환 기능
invalid-email-address Dec 9, 2022
aa7e517
test: 추가 남은 금액이 상품의 최저 가격보다 적거나, 모든 상품이 소진된 경우 잔돈 반환 기능테스트
invalid-email-address Dec 9, 2022
2059c15
fix: 추가 출력문추가
invalid-email-address Dec 9, 2022
9f81ddd
refactor: 수정 클린코드, 필요없는 변수제거
invalid-email-address Dec 9, 2022
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
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies {

java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
languageVersion = JavaLanguageVersion.of(11)
}
}

Expand Down
22 changes: 22 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 자판기

## 기능 목록

### 자판기
- [ ] 자판기가 보유하고 있는 금액으로 동전을 무작위로 생성하는 기능
- [ ] 자판기가 보유하고 있는 금액을 입력받는 기능
- [ ] 자판기가 보유한 동전을 출력하는 기능
- [ ] 상품명, 가격, 수량을 입력하여 상품을 추가하는 기능
- [ ] 상품 가격이 100원 이상인지 확인하는 기능
- [ ] 상품 가격이 10원으로 나누어지는지 확인하는 기능
- [ ] 상품명이 다른 상품명과 중복 되지않는지 확인하는 기능
- [ ] 수량이 양수 인지 확인하는 기능
- [ ] 남은 금액이 상품의 최저 가격보다 적거나, 모든 상품이 소진된 경우 잔돈 반환 기능
- [ ] 잔돈을 돌려줄 때 현재 보유한 최소 개수의 동전으로 잔돈 반환 기능
- [ ] 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환하는 기능
- [ ] 잔돈 출력 기능

### 사용자
- [ ] 사용자가 투입한 금액으로 상품을 구매하는 기능
- [ ] 투입 금액이 양수 인지 확인하는 기능
- [ ] 투입 금액 출력 기능
5 changes: 4 additions & 1 deletion src/main/java/vendingmachine/Application.java
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();
}
}
16 changes: 0 additions & 16 deletions src/main/java/vendingmachine/Coin.java

This file was deleted.

100 changes: 100 additions & 0 deletions src/main/java/vendingmachine/controller/VendingMachineController.java
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적으로 while(true)로 작성하면 읽는 도중에 종료조건을 한번 더 신경써야해서
저는 while의 조건구문에 항상 언제 종료된다는것을 표현하고는 합니다!

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);
}
}
}
}
50 changes: 50 additions & 0 deletions src/main/java/vendingmachine/domain/Money.java
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) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static 으로 선언해주신 이유가 있나요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

처음에는 정적 팩토리 메소드 from 함수에서 확인을해서 확인함수에서 static 을 붙였는데 생성자로 옮기면서 필요가 없어졌네요,,, 감사합니다

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;
}
}
33 changes: 33 additions & 0 deletions src/main/java/vendingmachine/domain/VendingMachine.java
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) {
Copy link
Member

Choose a reason for hiding this comment

The 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);
}
}
51 changes: 51 additions & 0 deletions src/main/java/vendingmachine/domain/coins/Coin.java
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
Copy link
Member

Choose a reason for hiding this comment

The 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);
Copy link

Choose a reason for hiding this comment

The 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;
}
}
69 changes: 69 additions & 0 deletions src/main/java/vendingmachine/domain/coins/Coins.java
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) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기능을 더 분리해볼 수 있을 것 같습니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤식으로 분리하실지 여쭤봐도될까요?!

Copy link

Choose a reason for hiding this comment

The 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();
    }
}

Copy link
Author

Choose a reason for hiding this comment

The 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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

number 라는 변수명은 너무 포괄적인 것 같습니다!
coinAmounts 를 기반으로 값이 생성되고 있으니 해당 맥락에 맞게 네이밍을 해주는 것도 좋을 것 같아요!

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)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coin.getKey() 가 아니라 그냥 coin 을 넘겨줘도 되지 않나요?

추가적으로 빈 맵을 선언하시는 게 거슬리시다면
CollectorstoMap 을 활용해보시는 것도 추천드려요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변수명 때문에 착각하신거 같아요 ㅠ coin 의 자료형은 coin 이 아닌 Entry<Coin, Integer> 여서 getKey()Coin 을 넘겨주었습니다.. 네이밍 너무 어렵습니다..

toMap 에 대해 알려주셔서 감사합니다. 다음에 활용해보겠습니다!

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interface 상단에 @FunctionalInterface 어노테이션을 선언하면 해당 인터페이스가 함수형 인터페이스임을 강조할 수 있습니다!

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);
}
}
Loading