-
Notifications
You must be signed in to change notification settings - Fork 358
[자판기] 전하영 리팩토링합니다. #172
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
base: main
Are you sure you want to change the base?
[자판기] 전하영 리팩토링합니다. #172
Changes from all commits
dbbff11
d7a9381
eb32acb
96c7f60
59a9700
a055ae3
bacd922
aa1c9ae
ec3b9f6
7974545
93e8abd
bfe1a10
4b3c3f3
d673a01
9de7ea4
507b4c8
1951b18
93f7616
d4d7b1a
2c530a5
4cbd3b8
dee099a
ec2a163
9345282
4ef0101
356d745
f568e50
0e3ba71
0885bd8
d8d3fc9
5c84a60
762c909
1a24493
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,58 @@ | ||
| # 자판기 | ||
|
|
||
| ## 기능 목록 | ||
|
|
||
|
|
||
| - 자판기가 보유하고 있는 금액 입력 받기 | ||
| - 입력값 검증 | ||
| - 숫자인지 | ||
| - 10의 배수인지 | ||
|
|
||
|
|
||
| - 자판기가 보유하고 있는 금액으로 동전을 무작위로 생성 | ||
|
|
||
|
|
||
| - 자판기가 보유하고 있는 동전 출력 | ||
|
|
||
|
|
||
| - 상품명, 가격, 수량 입력 받기 | ||
| - 입력값 검증 | ||
| - 숫자인지 | ||
| - 100원 이상인지 | ||
| - 10원으로 나누어 떨어지는지 | ||
|
|
||
|
|
||
| - 투입 금액 입력 받기 | ||
| - 입력값 검증 | ||
| - 숫자인지 | ||
| - 양수인지 | ||
|
|
||
|
|
||
| - 구매할 상품 입력 받기 | ||
|
|
||
|
|
||
| - 입력 받은 상품 구매 가능 여부 확인 | ||
| - 상품의 품절 여부 | ||
| - 잔액이 충분한지 | ||
| - 판매하는 상품인지 | ||
|
|
||
|
|
||
| - 상품 구매 | ||
| - 남은 투입금액 계산 | ||
| - 남은 상품 수량 계산 | ||
|
|
||
|
|
||
| - 잔돈 반환 여부 확인 | ||
| - 잔돈 반환 | ||
| - 남은 금액이 남은 상품의 최저 가격보다 적은 경우 | ||
| - 모든 상품이 소진된 경우 | ||
| - 잔돈 반환 X | ||
| - 상품 추가 구매 | ||
|
|
||
|
|
||
| - 잔돈 계산 | ||
| - 잔돈 전체 반환 가능 여부 확인 | ||
| - 잔돈이 자판기 보유금액보다 금액이 작은 경우 > 전체 반환 | ||
| - 잔돈이 자판기 보유금액보다 금액이 큰 경우 > 잔돈으로 반환할 수 있는 금액(보유금액)만 반환 | ||
|
|
||
| - 잔돈 출력 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,12 @@ | ||
| package vendingmachine; | ||
|
|
||
| import vendingmachine.controller.VendingMachineController; | ||
|
|
||
|
|
||
| public class Application { | ||
| public static void main(String[] args) { | ||
| // TODO: 프로그램 구현 | ||
| VendingMachineController vendingMachineController=new VendingMachineController(); | ||
| vendingMachineController.setVendingMachine(); | ||
| vendingMachineController.runVendingMachine(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| package vendingmachine.controller; | ||
|
|
||
| import vendingmachine.model.Balance; | ||
| import vendingmachine.model.Product; | ||
| import vendingmachine.model.Validator; | ||
| import vendingmachine.view.InputView; | ||
| import vendingmachine.view.OutputView; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| import static vendingmachine.model.Validator.validateProduct; | ||
|
|
||
| public class VendingMachineController { | ||
|
|
||
| private static Balance balance; | ||
| private static Map<String, Product> products; | ||
| private static int amountOfInput; | ||
|
|
||
| public void setVendingMachine() { | ||
| balance = new Balance(InputView.readBalance()); | ||
|
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. 그리고 static을 통해 클래스 변수로 지정하신 특별한 이유가 있나요? static 변수(클래스 변수)를 피해야 하는 이유는 여러가지지만 제가 명확히 기억하는 것은 2가지 인데,
**추가 다시 보니 setVendingMachine이 자판기 설정을 하는 메서드네요.. 나름 의미가 있는 메서드라는걸 코드 보면서 느꼈습니다. 단순히 객체를 초기화해주는게 아니었네요 (자판기 보유 동전 설정 + 상품 설정 역할을 하네요..) |
||
| OutputView.printBalanceCoin(balance.createCoin()); | ||
| saveProducts(); | ||
| amountOfInput = InputView.readAmountOfInput(); | ||
| OutputView.printAmountOfInput(amountOfInput); | ||
| } | ||
|
|
||
| public void runVendingMachine() { | ||
| while (canBuy()) { | ||
| buy(); | ||
|
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.
그 위에서 한번 출력을 하고 시작하네요 |
||
| OutputView.printAmountOfInput(amountOfInput); | ||
| } | ||
| OutputView.printChange(balance.calculateChangeCoin(amountOfInput)); | ||
| } | ||
|
|
||
| private boolean canBuy() { | ||
| List<Integer> leftProductsPrice = new ArrayList<Integer>(); | ||
| for (Map.Entry<String, Product> entry : products.entrySet()) { | ||
| if (entry.getValue().stockIsLeft()) { | ||
| leftProductsPrice.add(entry.getValue().getPrice()); | ||
| } | ||
| } | ||
| if (leftProductsPrice.isEmpty()) { | ||
| return false; | ||
| } | ||
| return amountOfInput >= leftProductsPrice.stream().mapToInt(Integer::intValue).min().getAsInt(); | ||
| } | ||
|
|
||
|
|
||
| public void saveProducts() throws IllegalArgumentException { | ||
| products = new HashMap<>(); | ||
| try { | ||
| String[] str = InputView.readProductInfo().split(";"); | ||
| for (String s : str) { | ||
| String[] productInfo = s.substring(1, s.length() - 1).split(","); | ||
| validateProduct(productInfo, products); | ||
| products.put(productInfo[0], new Product(productInfo[1], productInfo[2])); | ||
|
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. 궁금한게 Product 클래스를 생성하면 name과 price, 수량을 갖는 필드를 만들어 주는 것이 일반적인데, 이렇게 name은 product 클래스에 필드로 뺀 특별한 이유가 있나요? 그럼 굳이 Map<product이름, Product>가 필요 없어질것같아요
Author
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. 그냥 제가 Map을 좀 좋아하는 것 같아요..ㅋ ㅋㅋㅋㅋ 뭔가 이름을 key로 두면 필요한 정보 빼오거나 중복 체크하기 편한 느낌..? 근데 사실 저도 따로 빼는 게 조금 부자연스럽다고 생각하긴 했어요 |
||
| } | ||
| } catch (IllegalArgumentException e) { | ||
| System.out.println(e.getMessage()); | ||
| saveProducts(); | ||
|
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. 저는 다시 입력 받는것 또한 빼먹었네요..
Author
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. 다시 입력 받는 게 매우 귀찮은 부분이었죠..하하 |
||
| } | ||
| } | ||
|
|
||
|
|
||
| private void buy() { | ||
| try { | ||
| String buyingProduct = InputView.readBuyingProduct(); | ||
| Validator.validateBuyingProduct(buyingProduct, products, amountOfInput); | ||
| amountOfInput -= products.get(buyingProduct).getPrice(); | ||
| products.get(buyingProduct).reduceAmount(); | ||
| } catch (IllegalArgumentException e) { | ||
| System.out.println(e.getMessage()); | ||
| buy(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package vendingmachine.model; | ||
|
|
||
| import camp.nextstep.edu.missionutils.Randoms; | ||
| import vendingmachine.Coin; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.EnumMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public class Balance { | ||
| private final int balance; | ||
| List<Integer> numbers = new ArrayList<>(); | ||
| private final Map<Coin, Integer> coins = new EnumMap<>(Coin.class); | ||
| private final Map<Coin, Integer> changeCoins = new EnumMap<>(Coin.class); | ||
|
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. 오 이런게 있는 줄 몰랐어요 ㅋㅋ 저는 LinkedHashMap 써서 Coin enum 순서대로 넣어줬거든요
Author
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. 저도 처음 써봤어요ㅋㅋㅋㅋ 오히려 LinkedHashMap은 한번도 안 써봤네요.. |
||
|
|
||
| public Balance(int balance) { | ||
| this.balance = balance; | ||
| for (Coin coin : Coin.values()) { | ||
| numbers.add(coin.get()); | ||
| coins.put(coin, 0); | ||
| } | ||
| } | ||
|
|
||
| public Map<Coin, Integer> createCoin() { | ||
| int tmp = balance; | ||
|
|
||
| while (tmp > 0) { | ||
| int randomCoinAmount = Randoms.pickNumberInList(numbers); | ||
| if (tmp >= randomCoinAmount) { | ||
| Coin key = Coin.getEqualCoin(randomCoinAmount); | ||
| coins.put(key, coins.get(key) + 1); | ||
| tmp = tmp - randomCoinAmount; | ||
| } | ||
| } | ||
| return coins; | ||
| } | ||
|
|
||
| public Map<Coin, Integer> calculateChangeCoin(int change) { | ||
| if (change > balance) { | ||
| return coins; | ||
| } | ||
| for (Coin coin : Coin.values()) { | ||
| int numberOfBalanceCoin = coins.get(coin); | ||
| int neededCoins = change / coin.get(); | ||
| int inputCoins = Math.min(neededCoins, numberOfBalanceCoin); | ||
|
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. 전 왜 이 생각을 못했죠 전 neededCoin과 numberOfBalanceCoin 을 if문으로 경우 나눠서 처리해줬거든요
Author
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. 조금이라도 도움이 되었다니 다행이에요! |
||
| changeCoins.put(coin, inputCoins); | ||
| change -= coin.get() * inputCoins; | ||
| } | ||
| return changeCoins; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package vendingmachine.model; | ||
|
|
||
| import static vendingmachine.model.Validator.validateNum; | ||
| import static vendingmachine.model.Validator.validatePrice; | ||
|
|
||
| public class Price { | ||
|
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. 따로 클래스 뺀 이유가 있으신가요? Product안에서 int로 처리해줘도 될것 같은데 특별한 이유가 있는지 궁금해요
Author
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. 특별한 이유가 있다기 보다는 얼마 전에 원시 타입 포장 관련 글(https://tecoble.techcourse.co.kr/post/2020-05-29-wrap-primitive-type/)을 보고 처음 설계할 때 적용해 보려고 분리를 했었는데.. |
||
| private final int price; | ||
| Price(String price) { | ||
| this.price = validateNum(price); | ||
| validatePrice(this.price); | ||
| } | ||
|
|
||
| public int get() { | ||
| return price; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package vendingmachine.model; | ||
|
|
||
| import static vendingmachine.model.Validator.validateNegative; | ||
| import static vendingmachine.model.Validator.validateNum; | ||
|
|
||
| public class Product { | ||
|
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. product에 이름 필드를 넣는게 어떨까요? |
||
| private final Price price; | ||
| private int amount; | ||
|
|
||
| public Product(String price, String amount) throws IllegalArgumentException { | ||
| this.price = new Price(price); | ||
| this.amount = validateNum(amount); | ||
| validateNegative(this.amount); | ||
| } | ||
|
|
||
| public boolean stockIsLeft() { | ||
| return amount > 0; | ||
| } | ||
|
|
||
| public void reduceAmount() { | ||
| amount--; | ||
| } | ||
|
|
||
| public int getPrice() { | ||
| return price.get(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| package vendingmachine.model; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| import static vendingmachine.util.NumberConsts.MIN_PRICE; | ||
| import static vendingmachine.util.NumberConsts.MIN_UNIT; | ||
| import static vendingmachine.util.NumberConsts.NUMBER_OF_ELEMENTS; | ||
| import static vendingmachine.util.message.ErrorMessage.INVALID_FORMAT; | ||
| import static vendingmachine.util.message.ErrorMessage.INVALID_PRICE; | ||
| import static vendingmachine.util.message.ErrorMessage.INVALID_UNIT; | ||
| import static vendingmachine.util.message.ErrorMessage.IS_NEGATIVE; | ||
| import static vendingmachine.util.message.ErrorMessage.NOT_NUMBER; | ||
| import static vendingmachine.util.message.ProductErrorMessage.INSUFFICIENT_BALANCE; | ||
| import static vendingmachine.util.message.ProductErrorMessage.INVALID_NAME; | ||
| import static vendingmachine.util.message.ProductErrorMessage.NOT_FOR_SALE; | ||
| import static vendingmachine.util.message.ProductErrorMessage.OUT_OF_STOCK; | ||
|
|
||
| public class Validator { | ||
|
|
||
| public static int validateBalance(String input) throws IllegalArgumentException { | ||
| int balance = validateNum(input); | ||
| validateNegative(balance); | ||
|
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. 저는 음수인것을 validate를 안해줬네요 ㅎㅎ |
||
| validateInvalidUnit(balance); | ||
| return balance; | ||
| } | ||
|
|
||
| public static int validateAmountOfInput(String input) throws IllegalArgumentException { | ||
| int amountOfInput = validateNum(input); | ||
| validateNegative(amountOfInput); | ||
| return amountOfInput; | ||
| } | ||
|
|
||
| public static void validatePrice(int price) { | ||
| if (price < MIN_PRICE) { | ||
| throw new IllegalArgumentException(INVALID_PRICE.fullMessage()); | ||
| } | ||
| validateInvalidUnit(price); | ||
| } | ||
|
|
||
| public static void validateProduct(String[] productInfo, Map<String, Product> products) throws IllegalArgumentException { | ||
| if (productInfo.length != NUMBER_OF_ELEMENTS) { | ||
| throw new IllegalArgumentException(INVALID_FORMAT.fullMessage()); | ||
| } | ||
| if (products.containsKey(productInfo[0])) { | ||
|
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. 상품명이 중복되는 경우또한 저는 빼먹었네요 |
||
| throw new IllegalArgumentException(INVALID_NAME.fullMessage()); | ||
| } | ||
| } | ||
|
|
||
| public static void validateBuyingProduct(String buyingProduct, Map<String, Product> products, | ||
| int amountOfInput) throws IllegalArgumentException { | ||
| if (!products.containsKey(buyingProduct)) { | ||
| throw new IllegalArgumentException(NOT_FOR_SALE.fullMessage()); | ||
| } | ||
| if (products.get(buyingProduct).getPrice() > amountOfInput) { | ||
| throw new IllegalArgumentException(INSUFFICIENT_BALANCE.fullMessage()); | ||
| } | ||
| if (!products.get(buyingProduct).stockIsLeft()) { | ||
| throw new IllegalArgumentException(OUT_OF_STOCK.fullMessage()); | ||
| } | ||
| } | ||
|
|
||
| public static int validateNum(String input) throws IllegalArgumentException { | ||
| try { | ||
| return Integer.parseInt(input); | ||
| } catch (NumberFormatException e) { | ||
| throw new IllegalArgumentException(NOT_NUMBER.fullMessage()); | ||
| } | ||
| } | ||
|
|
||
| public static void validateNegative(int num) throws IllegalArgumentException { | ||
| if (num < 0) { | ||
| throw new IllegalArgumentException(IS_NEGATIVE.fullMessage()); | ||
| } | ||
| } | ||
|
|
||
| public static void validateInvalidUnit(int num) throws IllegalArgumentException { | ||
| if (num % MIN_UNIT != 0) { | ||
| throw new IllegalArgumentException(INVALID_UNIT.fullMessage()); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package vendingmachine.util; | ||
|
|
||
| public class NumberConsts { | ||
| public static final int MIN_PRICE = 100; | ||
| public static final int MIN_UNIT = 10; | ||
| public static final int NUMBER_OF_ELEMENTS = 3; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package vendingmachine.util.message; | ||
|
|
||
| import static vendingmachine.util.NumberConsts.MIN_PRICE; | ||
| import static vendingmachine.util.NumberConsts.MIN_UNIT; | ||
|
|
||
| public enum ErrorMessage { | ||
| ERROR_MESSAGE("[ERROR] "), | ||
| INVALID_PRICE(String.format("%d원 이상이어야 합니다.", MIN_PRICE)), | ||
| INVALID_UNIT(String.format("%d원으로 나누어떨어져야 합니다.", MIN_UNIT)), | ||
| INVALID_FORMAT("형식에 맞지 않습니다."), | ||
| NOT_NUMBER("형식에 맞지 않습니다."), | ||
| IS_NEGATIVE("음수를 입력할 수 없습니다."); | ||
|
|
||
| private final String message; | ||
|
|
||
| ErrorMessage(String message) { | ||
| this.message = message; | ||
| } | ||
|
|
||
| public String fullMessage() { | ||
| return ERROR_MESSAGE.message + message; | ||
| } | ||
| } |
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.
main 메서드에서 set -> runVendingMachine() 이라는 함수를 통해서 어떻게 보면 두가지 일을 하고 있는것 같아요. 코드를 봤는데, controller에서 run메서드 실행 전에 set은 반드시 되어야 하니까 run 메서드 안에서 setVendingMachine 메서드를 호출하는 방식이 좋지 않을까 생각해봐요!