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

[프리코스 6기 타임어택 스터디] 함지수 과제 제출합니다. #183

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
10 changes: 5 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ dependencies {
implementation 'com.github.woowacourse-projects:mission-utils:1.0.0'
}

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

test {
useJUnitPlatform()
Expand Down
76 changes: 76 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 구현 기능 목록


- [X] 반환되는 동전이 최소한이 되는 자판기를 구현 -> 가능한 한 적은 수의 동전 반환 = 큰 단위의 동전 최대한 많이 만들기


- [X] 자판기가 보유한 금액으로 동전을 무작위 생성
- [X] 자판기 보유 금액 입력받기
- [X] 10 으로 나눠떨어져야 함
- [X] 입력받은 자판기 보유 금액으로 동전 생성 (500원, 100원, 50원, 10원)
- [X] 투입 금액으로는 동전을 생성하지 않는다.


- [X] 상품명, 가격, 수량을 입력하여 상품을 추가할 수 있다.
- [X] 상품 가격은 100원부터 시작하며, 10원으로 나누어떨어져야 한다.
- [X] 상품이 여러개인 경우, 상품명이 중복되지 않아야 한다.
- [X] 입력 형식 예: "[콜라,1500,20];[사이다,1000,10]"


- [X] 사용자가 투입한 금액으로 상품을 구매할 수 있다.
- [X] 투입 금액을 입력받고 투입 금액을 출력하고 구매할 상품명을 입력받는다.


- [X] 잔돈 반환 조건
- [X] 남은 금액이 상품의 최저 가격보다 적은 경우 바로 잔돈을 돌려준다.
- [X] 모든 상품이 소진된 경우 바로 잔돈을 돌려준다.


- [ ] 잔돈 반환
- [ ] 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환. 반환되지 않은 금액은 자판기에 남는다.
- [ ] 잔돈을 돌려줄 때 현재 보유한 최소 개수의 동전으로 잔돈을 돌려준다.
- [ ] 지폐를 잔돈으로 반환하는 경우는 없다.


- [ ] 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException 발생, "[ERROR]"로 시작하는 에러 메시지를 출력 후, 해당 부분부터 다시 입력을 받는다.


- [ ] 코드 컨벤션 적용
- [ ] 메서드 10라인 이하
- [ ] else X
- [ ] indent 2까지
- [ ] 하드코딩 X (상수 처리)



---
- 전체 흐름 입출력 예시
```
자판기가 보유하고 있는 금액을 입력해 주세요.
450

자판기가 보유한 동전
500원 - 0개
100원 - 4개
50원 - 1개
10원 - 0개

상품명과 가격, 수량을 입력해 주세요.
[콜라,1500,20];[사이다,1000,10]

투입 금액을 입력해 주세요.
3000

투입 금액: 3000원
구매할 상품명을 입력해 주세요.
콜라

투입 금액: 1500원
구매할 상품명을 입력해 주세요.
사이다

투입 금액: 500원
잔돈
100원 - 4개
50원 - 1개
```
7 changes: 6 additions & 1 deletion src/main/java/vendingmachine/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package vendingmachine;

import vendingmachine.controller.MainController;
import vendingmachine.view.InputView;
import vendingmachine.view.OutputView;

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

This file was deleted.

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

import vendingmachine.domain.*;
import vendingmachine.dto.ExchangeCoinsDto;
import vendingmachine.dto.ItemDto;
import vendingmachine.dto.OrderDetailDto;
import vendingmachine.dto.VendingMachineCoinsDto;
import vendingmachine.view.InputView;
import vendingmachine.view.OutputView;

import java.util.EnumMap;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class MainController {
private final InputView inputView;
private final OutputView outputView;

public MainController(InputView inputView, OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}

public void run() {
VendingMachineCoins coins = createVendingMachineAmount();
outputView.printVendingMachineAmount(VendingMachineCoinsDto.from(coins));
Items items = createItems();
InsertedAmount insertedAmount = createInsertedAmount();
outputView.printInputAmount(insertedAmount.provideAmount());
boolean canBuyMore = true;
while (canBuyMore) {
OrderDetailDto orderDetailDto = createOrderDetailDto(insertedAmount.provideAmount(), items);
insertedAmount.updateAmount(orderDetailDto.getUpdatedInputAmount());
outputView.printInputAmount(insertedAmount.provideAmount());
canBuyMore = canBuyMore(items, insertedAmount);
}
generateExchangeCoins(coins, insertedAmount);
}

private void generateExchangeCoins(VendingMachineCoins coins, InsertedAmount insertedAmount) {
long amount = insertedAmount.provideAmount();
EnumMap<Coin, Long> exchangeCoins = coins.generateExchangeCoins(amount);
ExchangeCoinsDto exchangeCoinsDto = ExchangeCoinsDto.from(exchangeCoins);
outputView.printExchangeCoins(exchangeCoinsDto);
}

private boolean canBuyMore(Items items, InsertedAmount amount) {
return amount.isEqualOrLargerThan(items.findPurchasableMinimumPrice()) && !items.hasNoQuantity();
}

private VendingMachineCoins createVendingMachineAmount() {
return readUserInput(() -> {
long amount = inputView.readVendingMachineAmount();
return VendingMachineCoins.from(amount);
});
}

private Items createItems() {
return readUserInput(() -> {
List<Item> items = inputView.readItems().stream()
.map(ItemDto::toItem)
.collect(Collectors.toList());
return Items.from(items);
});
}

private InsertedAmount createInsertedAmount() {
return readUserInput(() -> {
long amount = inputView.readInputAmount();
return InsertedAmount.from(amount);
});
}

private OrderDetailDto createOrderDetailDto(long inputAmount, Items items) {
return readUserInput(() -> {
String itemName = inputView.readOrderItemName();
Item orderItem = items.buyItem(itemName, inputAmount);
long updatedInputAmount = inputAmount - orderItem.providePrice();
return OrderDetailDto.of(itemName, updatedInputAmount);
});
}

private <T> T readUserInput(Supplier<T> supplier) {
while (true) {
try {
return supplier.get();
} catch (IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}
}
}
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.EnumMap;
import java.util.concurrent.atomic.AtomicLong;

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 EnumMap<Coin, Long> generateMinimumCoins(long totalAmount) {
EnumMap<Coin, Long> coins = new EnumMap<>(Coin.class);
AtomicLong remainingAmount = new AtomicLong(totalAmount);

Arrays.stream(Coin.values()).forEach(coin -> {
long coinCount = remainingAmount.get() / coin.getAmount();
remainingAmount.addAndGet(-coinCount * coin.getAmount());
coins.put(coin, coinCount);
});

return coins;
}

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

public class InsertedAmount {
private long amount;

private InsertedAmount(long amount) {
this.amount = amount;
}

public static InsertedAmount from(long amount) {
return new InsertedAmount(amount);
}

public long provideAmount() {
return amount;
}

public void updateAmount(long amount) {
this.amount = amount;
}


public boolean isEqualOrLargerThan(long amount) {
return this.amount >= amount;
}
}
55 changes: 55 additions & 0 deletions src/main/java/vendingmachine/domain/Item.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package vendingmachine.domain;

import vendingmachine.utils.ItemValidator;

import static vendingmachine.exception.ErrorMessage.CANNOT_BUY_ORDER_ITEM;

public class Item {
private final String name;
private final long price;
private long quantity;

private Item(String name, long price, long quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}

public static Item of(String name, long price, long quantity) {
ItemValidator.validatePrice(price);

return new Item(name, price, quantity);
}

public void buyItem(long priceAmount) {
if (canBuy(priceAmount)) {
updateQuantity();
return;
}
throw new IllegalArgumentException(CANNOT_BUY_ORDER_ITEM.getMessage());
}

private void updateQuantity() {
quantity--;
}

public boolean canBuy(long priceAmount) {
return hasQuantity() && priceAmount >= price;
}

public boolean hasQuantity() {
return quantity > 0;
}

public String provideName() {
return name;
}

public long providePrice() {
return price;
}

public boolean hasNoQuantity() {
return quantity == 0;
}
}
57 changes: 57 additions & 0 deletions src/main/java/vendingmachine/domain/Items.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package vendingmachine.domain;

import vendingmachine.utils.ItemsValidator;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.OptionalLong;
import java.util.stream.Collectors;

import static vendingmachine.exception.ErrorMessage.*;

public class Items {
private final List<Item> items;

private Items(List<Item> items) {
this.items = items;
}

public static Items from(List<Item> items) {
validateUniqueName(items);
return new Items(items);
}

private static void validateUniqueName(List<Item> items) {
List<String> names = items.stream()
.map(Item::provideName)
.collect(Collectors.toList());
ItemsValidator.validateUniqueValue(names);
}

public Item buyItem(String itemName, long priceAmount) {
Item item = findItemByName(itemName);
item.buyItem(priceAmount);
return item;
}

private Item findItemByName(String name) {
return items.stream()
.filter(item -> item.provideName().equals(name))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(INVALID_ORDER_ITEM_NAME.getMessage()));
}

public long findPurchasableMinimumPrice() {
return items.stream()
.filter(Item::hasQuantity)
.mapToLong(Item::providePrice)
.min()
.orElseThrow(() -> new IllegalArgumentException(INVALID_ORDER_ITEM.getMessage()));
}

public boolean hasNoQuantity() {
return items.stream()
.allMatch(Item::hasNoQuantity);
}

}
Loading