Skip to content
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
11 changes: 10 additions & 1 deletion src/main/java/vendingmachine/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package vendingmachine;

import vendingmachine.coin.generator.RandomCoinGenerator;
import vendingmachine.controller.InitialController;
import vendingmachine.controller.PurchaseController;

public class Application {
private static final InitialController initialController = new InitialController();
private static final PurchaseController purchaseController = new PurchaseController();

public static void main(String[] args) {
// TODO: 프로그램 구현
VendingMachine vendingMachine = initialController.create(new RandomCoinGenerator());
purchaseController.purchase(vendingMachine);

}
}
16 changes: 0 additions & 16 deletions src/main/java/vendingmachine/Coin.java

This file was deleted.

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

import vendingmachine.exception.VendingMachineException;
import vendingmachine.menu.Menu;

public class Credit {
private int money;

public Credit(int money){
validateMoney(money);
this.money = money;
}

private void validateMoney(int money) {
if(money < 100 || money % 10 != 0){
throw VendingMachineException.INVALID_MONEY_VALUE.makeException();
}
}


public boolean canPurchase(int price){
return money >= price;
}

public void purchase(Menu menu){
if(menu.getPrice() > money){
throw VendingMachineException.CANT_PURCHASE.makeException();
}
money -= menu.getPrice();
}

public int getMoney() {
return money;
}
}
41 changes: 41 additions & 0 deletions src/main/java/vendingmachine/VendingMachine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package vendingmachine;

import java.util.Map;
import vendingmachine.coin.Coin;
import vendingmachine.coin.Coins;
import vendingmachine.menu.Menu;
import vendingmachine.menu.Menus;

public class VendingMachine {
private final Menus menus;
private final Credit credit;
private final Coins coins;

public VendingMachine(Menus menus, Credit credit, Coins coins) {
this.menus = menus;
this.credit = credit;
this.coins = coins;
}

public boolean isSellable(){
if(menus.isSoldOut()){
return false;
}
int minPrice = menus.getMinPrice();
return credit.canPurchase(minPrice);
}

public int getRemainMoney(){
return credit.getMoney();
}

public void purchase(String menuName){
Menu menu = menus.getMenu(menuName);
credit.purchase(menu);
menus.purchase(menu);
}

public Map<Coin, Integer> giveChange(){
return coins.giveChange(credit.getMoney());
}
}
30 changes: 30 additions & 0 deletions src/main/java/vendingmachine/coin/Coin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package vendingmachine.coin;

import java.util.Arrays;
import vendingmachine.exception.VendingMachineException;

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 getCoins(int amount){
return Arrays.stream(values())
.filter(coin -> coin.amount == amount)
.findFirst()
.orElseThrow(VendingMachineException.INVALID_COIN_AMOUNT::makeException);
}


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

import java.util.EnumMap;
import java.util.Map;

public class Coins {
private final Map<Coin, Integer> coins;

public Coins(Map<Coin, Integer> coins){
this.coins = coins;
}

public int getCounts(Coin coin){
return coins.get(coin);
}

public Map<Coin, Integer> giveChange(int changeMoney){
Map<Coin, Integer> changes = new EnumMap<>(Coin.class);
for(Coin coin: Coin.values()){
if(coin.getAmount() > changeMoney){
changes.remove(coin);
continue;
}
int count = getCount(changeMoney, coin);
changes.put(coin, count);
changeMoney -= coin.getAmount() * count;
}

return changes;
}

private int getCount(int changeMoney, Coin coin) {
int count = coins.get(coin);
while(count * coin.getAmount() > changeMoney){
count--;
}
return count;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package vendingmachine.coin.generator;

import java.util.Map;
import vendingmachine.coin.Coin;

public interface CoinGenerator {
Map<Coin, Integer> getCoins(int totalMoney);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package vendingmachine.coin.generator;

import camp.nextstep.edu.missionutils.Randoms;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import vendingmachine.coin.Coin;

public class RandomCoinGenerator implements CoinGenerator{

@Override
public Map<Coin, Integer> getCoins(int totalMoney) {
EnumMap<Coin, Integer> coins = initCoins();
List<Integer> integers = initIntegers();
while(totalMoney > 0){
int amount = Randoms.pickNumberInList(integers);
if(amount > totalMoney){
continue;
}
Coin coin = Coin.getCoins(amount);
coins.put(coin, coins.get(coin) + 1);
totalMoney -= amount;
}
return coins;
}

private static EnumMap<Coin, Integer> initCoins() {
EnumMap<Coin, Integer> coins = new EnumMap<>(Coin.class);
Arrays.stream(Coin.values())
.forEach(coin -> coins.put(coin, 0));
return coins;
}

private static List<Integer> initIntegers() {
List<Integer> integers = new ArrayList<>();
integers.add(500);
integers.add(100);
integers.add(50);
integers.add(10);
return integers;
}
}
56 changes: 56 additions & 0 deletions src/main/java/vendingmachine/controller/InitialController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package vendingmachine.controller;

import java.util.List;
import java.util.function.Supplier;
import vendingmachine.Credit;
import vendingmachine.VendingMachine;
import vendingmachine.coin.Coins;
import vendingmachine.coin.generator.CoinGenerator;
import vendingmachine.exception.RetryExceptionHandler;
import vendingmachine.exception.VendingMachineException;
import vendingmachine.menu.Menus;
import vendingmachine.view.InputView;
import vendingmachine.view.OutputView;

public class InitialController {
public VendingMachine create(CoinGenerator generator){
Coins coins = get(() -> makeCoins(generator));
Menus menus = get(this::makeMenus);
Credit credit = get(this::getCredit);
return new VendingMachine(menus, credit, coins);
}

private Coins makeCoins(CoinGenerator generator) {
int coinMoney = getCoinMoney();
Coins coins = new Coins(generator.getCoins(coinMoney));
OutputView.printCoins(coins);

Choose a reason for hiding this comment

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

혁수님! OutputView를 static으로 불러올 수도 있고 객체를 만들어서 컨트롤러로 주입해줄 수 있는데 혹시 그중에 static을 사용하신 이유가 궁금해요
스터디때 최대한 static 사용을 지양하려고 고민 중이다! 라고 들었던 것 같아서 여쭤봅니당 👍

Copy link
Author

Choose a reason for hiding this comment

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

우선 static import를 하지 않은 이유는 스터디때 말씀 드린것과 마찬가지로 해당 메서드의 위치를 확실하게 명시하는 것이 저의 취향이기 때문이에요 ㅎㅎ

또, 의존성 주입을 사용하지 않은 것은 해당 부분에 대한 변경 가능성 없었기 때문입니다.
5시간이라는 짧은 시간동안 기능을 모두 완성하기 위해선 특정 부분에서는 트레이드 오프가 존재할 수 밖에 없다고 생각하는데 저에겐 이 부분이 트레이드 오프에 해당했어요.

만약 Randoms를 사용하는 클래스와 같이 테스트 과정에서 다른 클래스로 변경될 가능성이 있다면 의존성을 주입받았을 텐데요. 하지만 이번의 경우 출력을 하는 View를 변경할 일이 적어도 5시간 내에는 존재하지 않았기 때문에 의존성 주입을 사용하지 않았습니다.

마지막으로 OutputView의 메서드를 static으로 만든 것은 실제 웹 MVC에서도 View에 해당하는 html은 정적 파일이기에 그와 비슷하게 구현해 보자는 생각 때문이었습니다.
다만, 뭔가 이 부분이 원래 제가 사용하던 방법과 달라지다 보니 어색한 감이 있어 앞으론 OutputView도 하나의 객체로 생성하고 메서드를 호출할 것 같긴 해요!!

Choose a reason for hiding this comment

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

아 그러면 마지막에 말씀하신 내용은

  • 의존성 주입은 사용하지 않음
  • 메서드는 static으로 선언하지 않을 것

으로 Controller에서
InputView inputView = new InputView()와 같이
객체를 선언해서 사용하겠다는 말씀일까요?? 🤔

Copy link
Author

Choose a reason for hiding this comment

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

네, 지금은 그게 제일 저한테 익숙한 방법이라서요!

return coins;
}

private int getCoinMoney() {
int coinMoney = InputView.getCoinMoney();
validateMoney(coinMoney);
return coinMoney;
}

private void validateMoney(int coinMoney) {
if(coinMoney < 0 || coinMoney % 10 != 0){
throw VendingMachineException.INVALID_MONEY_VALUE.makeException();
}
}

private Menus makeMenus() {
List<String> menus = InputView.getMenus();

return new Menus(menus);
}

private Credit getCredit(){
int initMoney = InputView.getInitMoney();
return new Credit(initMoney);
}

private <T> T get(Supplier<T> supplier){
return RetryExceptionHandler.get(supplier);
}
}
36 changes: 36 additions & 0 deletions src/main/java/vendingmachine/controller/PurchaseController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package vendingmachine.controller;

import java.util.Map;
import vendingmachine.VendingMachine;
import vendingmachine.coin.Coin;
import vendingmachine.exception.RetryExceptionHandler;
import vendingmachine.view.InputView;
import vendingmachine.view.OutputView;

public class PurchaseController {
public void purchase(VendingMachine machine) {
while (machine.isSellable()) {
RetryExceptionHandler.run(() ->purchaseMenu(machine));
}
printResult(machine);
}

private void printResult(VendingMachine machine) {
printRemainMoney(machine);
Map<Coin, Integer> coinIntegerMap = machine.giveChange();
OutputView.printChange(coinIntegerMap);
}

private static void purchaseMenu(VendingMachine machine) {
printRemainMoney(machine);
String menuName = InputView.getMenuName();
machine.purchase(menuName);
}

private static void printRemainMoney(VendingMachine machine) {
int remainMoney = machine.getRemainMoney();
OutputView.printRemainMoney(remainMoney);
}


}
33 changes: 33 additions & 0 deletions src/main/java/vendingmachine/exception/RetryExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package vendingmachine.exception;

import java.util.function.Supplier;
import vendingmachine.view.io.Printer;

public class RetryExceptionHandler {
private RetryExceptionHandler(){}

public static <T> T get(Supplier<T> supplier){
while(true) {
try{
return supplier.get();
} catch (IllegalArgumentException e){
Printer.printMessage(e.getMessage());
} finally {
Printer.printMessage("");
}
}
}

public static void run(Runnable runnable){
while(true) {
try{
runnable.run();
return;
} catch (IllegalArgumentException e){
Printer.printMessage(e.getMessage());
} finally {
Printer.printMessage("");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package vendingmachine.exception;

public enum VendingMachineException {
INVALID_NUMBER_FORMAT("숫자를 입력해 주세요"),
INVALID_MONEY_VALUE("입력 가격이 잘못되었습니다."),
INVALID_STRING_FORMAT("입력 형식이 잘못되었습니다."),
END_WITH_DELIMITER("입력의 마지막이 구분자로 끝났습니다."),

INVALID_COIN_AMOUNT("해당 금액의 동전을 찾을 수 없습니다."),
EMPTY_MENU_LIST("메뉴가 입력되지 않았습니다."),
NO_MENU_FOUNDED("해당 메뉴를 찾을 수 없습니다."),

CANT_PURCHASE("구매할 수 없는 상품입니다."),
MENU_AMOUNT_MUST_POSITIVE("메뉴의 수량은 0보다 큰 값이어야 합니다."),
NO_INPUT_FOUNDED("입력이 존재하지 않습니다."),
;

private static final String PREFIX = "[ERROR] ";
private final String messsage;

VendingMachineException(String messsage) {
this.messsage = messsage;
}

public String getMesssage() {
return PREFIX + messsage;
}

public IllegalArgumentException makeException(){
return new IllegalArgumentException(getMesssage());
}
}
Loading