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

[자판기] 이현수 #195

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
127 changes: 104 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,111 @@
# 미션 - 자판기

## 🔍 진행방식
## 🔍 진행 방식

- 미션은 **기능 요구사항, 프로그래밍 요구사항, 과제 진행 요구사항** 세 가지로 구성되어 있다.
- 세 개의 요구사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
- 기능 요구사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.
- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다.
- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
- 기능 요구 사항에 기재되지 않은 내용은 **스스로 판단하여 구현한다.**

## ✉️ 미션 제출 방법
## 📮 미션 제출 방법

- 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다.
- GitHub을 활용한 제출 방법은 [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 를 참고해 제출한다.
- GitHub에 미션을 제출한 후 [우아한테크코스 지원 플랫폼](https://apply.techcourse.co.kr) 에 접속하여 프리코스 과제를 제출한다.
- 자세한 방법은 [링크](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제출-가이드) 를 참고한다.
- **Pull Request만 보내고, 지원 플랫폼에서 과제를 제출하지 않으면 최종 제출하지 않은 것으로 처리되니 주의한다.**
- GitHub을 활용한 제출 방법은 [우아한테크코스 프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고해 제출한다.
- GitHub에 미션을 제출한 후 슬랙 `#미션-제출` 채널에 PR 주소를 올린다.
- **Pull Request만 보내고 Slack에 메시지를 보내지 않으면 리뷰를 하지 않으므로 주의한다.**

## ✔️ 과제 제출 전 체크리스트 - 0점 방지
## 🚨 과제 제출 전 체크 리스트

- 터미널에서 `java -version`을 실행해 자바 8인지 확인한다. 또는 Eclipse, IntelliJ IDEA와 같은 IDE의 자바 8로 실행하는지 확인한다.
- 터미널에서 맥 또는 리눅스 사용자의 경우 `./gradlew clean test`, 윈도우 사용자의 경우 `gradlew.bat clean test` 명령을 실행했을 때 모든 테스트가 아래와 같이 통과하는지 확인한다.
- 기능 구현을 모두 정상적으로 했더라도 **요구 사항에 명시된 출력값 형식을 지켜야한다.**
- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행했을 때 모든 테스트가 성공하는지 확인한다.

### 테스트 실행 가이드

- 터미널에서 `java -version`을 실행하여 Java 버전이 11인지 확인한다. 또는 Eclipse 또는 IntelliJ IDEA와 같은 IDE에서 Java 11로 실행되는지 확인한다.
- 터미널에서 Mac 또는 Linux 사용자의 경우 `./gradlew clean test` 명령을 실행하고,
Windows 사용자의 경우 `gradlew.bat clean test` 명령을 실행할 때 모든 테스트가 아래와 같이 통과하는지 확인한다.

```
BUILD SUCCESSFUL in 0s
```

---

## 🚀 기능 목록
### 예외
- 잘못된 금액 입력시 `[ERROR] 금액은 숫자여야 합니다.`예외 발생
- 상품명, 가격, 수량입력 형식이 잘못된 경우
- `[ERROR] {상품명, 가격, 수량} 입력 형식을 확인해주세요.` 예외 발생

### 금액
- 금액은 숫자이다.
- 금액은 1원 단위가 될 수 없다.(ex 1001원)

### 가격
- 가격은 숫자이다.
- 가격은 100원 이상이고 10원으로 나누어 떨어져야 한다.

### 수량
- 수량은 0개보다 작을 수 없다.

### 주문
- 사용자가 구매하고자 하는 상품의 이름으로 주문한다.

### 상품
- 상품은 이름, 가격, 수량이 있다.
- 상품의 가격은 100원 이상이고, 10원으로 나누어 떨어져야 한다.

### 잔돈
- 잔돈은 동전으로 이루어져 있다.
- 제공된 Coin enum을 사용한다.

### 자판기
- 잔돈을 가지고 있다.
- 자판기에 상품을 채워넣을 수 있다.
- 사용자가 금액을 투입할 수 있다.
- 남은 금액이 상품의 최저 가격보다 적거나 모든 상품이 소진된 경우 바로 잔돈을 돌려준다.
- 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환한다.

### 입력
- 자판기가 보유하고 있는 금액을 입력한다.
```angular2html
자판기가 보유하고 있는 금액을 입력해 주세요.
450
```
- 상품명, 가격, 수량은 쉼표로, 개별 상품은 대괄호 []로 묶어서 세미콜론;으로 구분한다.
```angular2html
[콜라,1500,20];[사이다,1000,10]
```
- 투입 금액을 입력한다.
```angular2html
투입 금액을 입력해 주세요.
3000
```
- 구매할 상품명을 입력한다.
```angular2html
구매할 상품명을 입력해 주세요.
사이다
```

### 출력
- 자판기가 보유한 동전을 출력한다.
```angular2html
자판기가 보유한 동전
500원 - 0개
100원 - 4개
50원 - 1개
10원 - 0개
```
- 투입 금액을 출력한다.
```angular2html
투입 금액: 3000원
```
- 잔돈을 출력한다.
```angular2html
잔돈
100원 - 4개
50원 - 1개
```

## 🚀 기능 요구사항

반환되는 동전이 최소한이 되는 자판기를 구현한다.
Expand Down Expand Up @@ -112,10 +193,10 @@ BUILD SUCCESSFUL in 0s

## 🎱 프로그래밍 요구사항

- 프로그램을 실행하는 시작점은 `Application`의 `main()`이다.
- JDK 8 버전에서 실행 가능해야 한다. **JDK 8에서 정상 동작하지 않을 경우 0점 처리**한다.
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- https://naver.github.io/hackday-conventions-java
- JDK 11 버전에서 실행 가능해야 한다.
- 프로그램 실행의 시작점은 `Application`의 `main()`이다.
- `build.gradle` 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다.
- [Java 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java) 가이드를 준수하며 프로그래밍한다.
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
Expand Down Expand Up @@ -154,14 +235,14 @@ public enum Coin {
- JDK에서 기본 제공하는 Random, Scanner API 대신 `camp.nextstep.edu.missionutils`에서 제공하는 `Randoms`, `Console` API를 활용해 구현해야 한다.
- Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms`의 `pickNumberInList()`를 활용한다.
- 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다.
- 프로그램 구현을 완료했을 때 `src/test/java` 디렉터리의 `ApplicationTest`에 있는 모든 테스트 케이스가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.**
- 프로그램 구현을 완료했을 때 `src/test/java` 디렉터리의 `ApplicationTest`에 있는 모든 테스트 케이스가 성공해야 한다.

---

## 📈 과제 진행 요구사항
## ✏️ 과제 진행 요구 사항

- 미션은 [java-vendingmachine-precourse](https://github.com/woowacourse/java-vendingmachine-precourse) 저장소를 Fork/Clone해 시작한다.
- **기능을 구현하기 전에 java-vendingmachine-precourse/docs/README.md 파일에 구현할 기능 목록을 정리**해 추가한다.
- **Git의 커밋 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위**로 추가한다.
- [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 참고해 commit log를 남긴다.
- 과제 진행 및 제출 방법은 [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 를 참고한다.
- 미션은 [java-vendingmachine](https://github.com/grow-up-study/java-vendingmachine) 저장소를 Fork & Clone해 시작한다.
- 과제 진행 및 제출 방법은 [우아한테크코스 프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고한다.
- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가한다.
- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가한다.
- [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성한다.
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.run();
}
}
103 changes: 103 additions & 0 deletions src/main/java/vendingmachine/controller/Parser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package vendingmachine.controller;

import java.util.Arrays;
import java.util.stream.Collectors;
import vendingmachine.exception.AmountNumberFormatException;
import vendingmachine.exception.PriceNumberFormatException;
import vendingmachine.exception.ProductFormatException;
import vendingmachine.exception.QuantityNumberFormatException;
import vendingmachine.model.Amount;
import vendingmachine.model.MachineAmount;
import vendingmachine.model.Name;
import vendingmachine.model.Order;
import vendingmachine.model.Price;
import vendingmachine.model.Product;
import vendingmachine.model.Products;
import vendingmachine.model.Quantity;

public class Parser {

private static final String PRODUCT_SEPARATOR = ";";
private static final char PRODUCT_START_WITH_CHAR = '[';
private static final char PRODUCT_END_WITH_CHAR = ']';
private static final String PRODUCT_START_WITH_STRING = "[";
private static final String PRODUCT_END_WITH_STRING = "]";
private static final String PRODUCT_INFO_SEPARATOR = ",";
private static final int MIN_PRODUCT_INFO_STRING_SIZE = 2;
private static final int PRODUCT_INFO_SIZE = 3;
private static final int PRODUCT_NAME_INDEX = 0;
private static final int PRODUCT_PRICE_INDEX = 1;
private static final int PRODUCT_QUANTITY_INDEX = 2;


private Parser() {
}

public static MachineAmount parseMachineAmount(String amount) {
try {
return new MachineAmount(Integer.parseInt(amount));
}catch (NumberFormatException exception) {
throw new AmountNumberFormatException();
}
}

public static Amount parseAmount(String amount) {
try {
return new Amount(Integer.parseInt(amount));
}catch (NumberFormatException exception) {
throw new AmountNumberFormatException();
}
}

public static Products parseProducts(String products) {
String[] product = products.split(PRODUCT_SEPARATOR);
return new Products(Arrays.stream(product).map(Parser::parseProduct).collect(Collectors.toList()));
}

private static Product parseProduct(String product) {
validateProductString(product);
String replaced = product.replace(PRODUCT_START_WITH_STRING, "").replace(PRODUCT_END_WITH_STRING, "");
String[] productInfo = replaced.split(PRODUCT_INFO_SEPARATOR);
validateProductInfo(productInfo);
return new Product(
new Name(productInfo[PRODUCT_NAME_INDEX]),
parsePrice(productInfo[PRODUCT_PRICE_INDEX]),
parseQuantity(productInfo[PRODUCT_QUANTITY_INDEX])
);
}

private static void validateProductInfo(String[] productInfo) {
if (productInfo.length != PRODUCT_INFO_SIZE) {
throw new ProductFormatException();
}
}

private static void validateProductString(String product) {
if (product.toCharArray().length <= MIN_PRODUCT_INFO_STRING_SIZE) {
throw new ProductFormatException();
}
if (!(product.charAt(0) == PRODUCT_START_WITH_CHAR && product.charAt(product.length() - 1) == PRODUCT_END_WITH_CHAR)) {
throw new ProductFormatException();
}
}

private static Price parsePrice(String price) {
try {
return new Price(Integer.parseInt(price));
}catch (NumberFormatException exception) {
throw new PriceNumberFormatException();
}
}

private static Quantity parseQuantity(String quantity) {
try {
return new Quantity(Integer.parseInt(quantity));
}catch (NumberFormatException exception) {
throw new QuantityNumberFormatException();
}
}

public static Order parseOrder(String productName) {
return new Order(new Name(productName));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package vendingmachine.controller;

import java.util.function.Supplier;
import vendingmachine.dto.ChangeDto;
import vendingmachine.exception.IllegalArgumentBaseException;
import vendingmachine.model.Amount;
import vendingmachine.model.Change;
import vendingmachine.model.MachineAmount;
import vendingmachine.model.Order;
import vendingmachine.model.Products;
import vendingmachine.model.VendingMachine;
import vendingmachine.service.VendingMachineService;
import vendingmachine.view.InputView;
import vendingmachine.view.OutputView;

public class VendingMachineController {

private final InputView inputView = new InputView();
private final OutputView outputView = new OutputView();
private VendingMachineService vendingMachineService;

public void run() {
Change change = handleInput(this::inputMachineAmount);
outputView.printMachineState(new ChangeDto(change));
Products products = handleInput(this::inputProducts);
Amount amount = handleInput(this::inputAmount);
vendingMachineService = new VendingMachineService(new VendingMachine(change, products, amount));
buy();
}

private void buy() {
while(vendingMachineService.isBuyable()) {
buyProduct();
}
printResultChange();
}

private void printResultChange() {
outputView.printRemainAmount(vendingMachineService.getAmount());
outputView.printChange(vendingMachineService.getChange());
}

private void buyProduct() {
outputView.printRemainAmount(vendingMachineService.getAmount());
handleInput(() -> vendingMachineService.buy(inputOrder()));
}

private Order inputOrder() {
return Parser.parseOrder(inputView.inputBuyProductName());
}

private Amount inputAmount() {
return Parser.parseAmount(inputView.inputBuyAmount());
}

private Change inputMachineAmount() {
MachineAmount amount = Parser.parseMachineAmount(inputView.inputMachineAmount());
return new Change(amount);
}

private Products inputProducts() {
return Parser.parseProducts(inputView.inputProducts());
}

private <T> T handleInput(Supplier<T> inputSupplier) {
while (true) {
try {
return inputSupplier.get();
} catch (IllegalArgumentBaseException exception) {
outputView.printErrorMessage(exception.getMessage());
}
}
}

private void handleInput(Runnable runnable) {
while (true) {
try {
runnable.run();
return;
} catch (IllegalArgumentBaseException exception) {
outputView.printErrorMessage(exception.getMessage());
}
}
}
}
17 changes: 17 additions & 0 deletions src/main/java/vendingmachine/dto/ChangeDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package vendingmachine.dto;

import java.util.LinkedHashMap;
import java.util.Map;
import vendingmachine.model.Change;

public class ChangeDto {
private final Map<Integer, Integer> change = new LinkedHashMap<>();

public ChangeDto(Change change) {
change.getChangeMoney().forEach(((coin, count) -> this.change.put(coin.getAmount(), count)));
}

public Map<Integer, Integer> getChange() {
return change;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package vendingmachine.exception;

public class AmountFormatException extends IllegalArgumentBaseException {

public AmountFormatException() {
super(ExceptionMessage.AMOUNT_FORMAT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package vendingmachine.exception;

public class AmountNotEnoughException extends IllegalArgumentBaseException {

public AmountNotEnoughException() {
super(ExceptionMessage.AMOUNT_NOT_ENOUGH);
}
}
Loading