From 9691cb62f3d8cc39753841594a8a413dabc9d8b7 Mon Sep 17 00:00:00 2001 From: suhyun Date: Sat, 25 Nov 2023 09:51:32 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=EC=9E=90=ED=8C=90=EA=B8=B0?= =?UTF-8?q?=EA=B0=80=20=EB=B3=B4=EC=9C=A0=ED=95=9C=20=EA=B8=88=EC=95=A1?= =?UTF-8?q?=EC=9D=84=20=EC=9E=85=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 7 +++ src/main/java/domain/PossesionAmount.java | 24 +++++++++ .../java/domain/constant/AmountConstant.java | 24 +++++++++ src/main/java/domain/wrapper/Amount.java | 53 +++++++++++++++++++ .../java/service/PossessionAmountService.java | 9 ++++ .../java/util/message/ExceptionMessage.java | 35 ++++++++++++ src/main/java/util/message/InputMessage.java | 24 +++++++++ src/main/java/view/InputView.java | 31 +++++++++++ src/test/java/domain/wrapper/AmountTest.java | 43 +++++++++++++++ 9 files changed, 250 insertions(+) create mode 100644 docs/README.md create mode 100644 src/main/java/domain/PossesionAmount.java create mode 100644 src/main/java/domain/constant/AmountConstant.java create mode 100644 src/main/java/domain/wrapper/Amount.java create mode 100644 src/main/java/service/PossessionAmountService.java create mode 100644 src/main/java/util/message/ExceptionMessage.java create mode 100644 src/main/java/util/message/InputMessage.java create mode 100644 src/main/java/view/InputView.java create mode 100644 src/test/java/domain/wrapper/AmountTest.java diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..e5e0f623c --- /dev/null +++ b/docs/README.md @@ -0,0 +1,7 @@ +### 기능 요구사항 + +[ ✅ ] 사용자는 자판기가 보유하고 있는 금액을 입력한다. + +[ ] 자판기가 보유하고 있는 금액으로 동전을 무작위로 생성한다. + +- 투입금액으로 동전을 생성하지 않는다. == Coin클래스로 동전을 생성한다. \ No newline at end of file diff --git a/src/main/java/domain/PossesionAmount.java b/src/main/java/domain/PossesionAmount.java new file mode 100644 index 000000000..231e789c4 --- /dev/null +++ b/src/main/java/domain/PossesionAmount.java @@ -0,0 +1,24 @@ +package domain; + +import domain.wrapper.Amount; + +import java.util.HashMap; + +public class PossesionAmount { + + private final Amount amount; + private PossesionAmount(final String possessionAmount){ + this.amount = Amount.create(possessionAmount); + } + + public static PossesionAmount create(final String possessionAmount){ + return new PossesionAmount(possessionAmount); + } + + //쉽게 말해, getter를 통해 얻은 상태값으로 하려고 했던 '행동'을 + // 그 상태값을 가진 객체가 하도록 '행동'의 주체를 옮기는 것이다. + + public int getAmount(){ + return amount.getAmount(); + } +} diff --git a/src/main/java/domain/constant/AmountConstant.java b/src/main/java/domain/constant/AmountConstant.java new file mode 100644 index 000000000..d095d0688 --- /dev/null +++ b/src/main/java/domain/constant/AmountConstant.java @@ -0,0 +1,24 @@ +package domain.constant; + +import util.EnumUtil; + +public enum AmountConstant implements EnumUtil { + COIN_TEN(10), + ZERO(0); + + private final int number; + + AmountConstant(final int number){ + this.number = number; + } + + @Override + public String getKey() { + return name(); + } + + @Override + public Integer getValue() { + return number; + } +} diff --git a/src/main/java/domain/wrapper/Amount.java b/src/main/java/domain/wrapper/Amount.java new file mode 100644 index 000000000..113e5051d --- /dev/null +++ b/src/main/java/domain/wrapper/Amount.java @@ -0,0 +1,53 @@ +package domain.wrapper; + +import domain.constant.AmountConstant; + +import static util.message.ExceptionMessage.*; + +public class Amount { + private final int amount; + + private Amount(final String possesionAmount){ + validateNameBlank(possesionAmount); + int amount = validateType(possesionAmount); + this.amount = validateDivisibleBy10(validateRange(amount)); + } + + public static Amount create(final String possesionAmount){ + return new Amount(possesionAmount); + } + + public int getAmount(){ + return amount; + } + + private void validateNameBlank(final String possesionAmount) { + if (possesionAmount == null || possesionAmount.trim().isEmpty()) { + throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "보유금액")); + } + } + + private int validateType(final String amount) { + int count; + try { + count = Integer.parseInt(amount); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format(TYPE_MESSAGE.getValue(), "보유금액")); + } + return count; + } + + private int validateDivisibleBy10(final int amount){ + if(amount % AmountConstant.COIN_TEN.getValue() != AmountConstant.ZERO.getValue()){ + throw new IllegalArgumentException(String.format(TEN_UNIT_MESSAGE.getValue(), AmountConstant.ZERO.getValue())); + } + return amount; + } + + private int validateRange(final int amount) { + if (amount < AmountConstant.ZERO.getValue()) { + throw new IllegalArgumentException(String.format(RANGE_MESSAGE.getValue(), AmountConstant.ZERO.getValue())); + } + return amount; + } +} diff --git a/src/main/java/service/PossessionAmountService.java b/src/main/java/service/PossessionAmountService.java new file mode 100644 index 000000000..29e870c2a --- /dev/null +++ b/src/main/java/service/PossessionAmountService.java @@ -0,0 +1,9 @@ +package service; + +import domain.PossesionAmount; + +public class PossessionAmountService { + public PossesionAmount createPossessionAmount(final String possessionAmount){ + return PossesionAmount.create(possessionAmount); + } +} diff --git a/src/main/java/util/message/ExceptionMessage.java b/src/main/java/util/message/ExceptionMessage.java new file mode 100644 index 000000000..a10f8c4d6 --- /dev/null +++ b/src/main/java/util/message/ExceptionMessage.java @@ -0,0 +1,35 @@ +package util.message; + +import util.EnumUtil; + +public enum ExceptionMessage implements EnumUtil { + BLANK_MESSAGE("%s은(는) 빈 값이 들어올 수 없습니다.") + , LENGTH_MESSAGE("%d글자를 초과하였습니다.") + , INPUT_MESSAGE("입력 중에 예기치 못한 오류가 발생하였습니다. 예외 메시지: %s") + , TYPE_MESSAGE("%s은(는) 숫자만 입력할 수 있습니다.") + , RANGE_MESSAGE("%d 보다 큰 값을 입력해 주세요.") + , TEN_UNIT_MESSAGE("%d원 단위로 입력해 주세요.") + , DUPLICATE_MESSAGE("중복된 값을 입력할 수 없습니다.") + , NO_RESOURCE_MESSAGE("%s(이)가 존재하지 않습니다.") + , NOT_COIN_MESSAGE("해당하는 %d원 동전이 존재하지 않습니다."); + private static final String ERROR_TAG = "[ERROR] "; + private final String message; + + public String getMessage(){ + return message; + } + + ExceptionMessage(final String message) { + this.message = ERROR_TAG + message; + } + + @Override + public String getKey() { + return name(); + } + + @Override + public String getValue() { + return message; + } +} diff --git a/src/main/java/util/message/InputMessage.java b/src/main/java/util/message/InputMessage.java new file mode 100644 index 000000000..112b78221 --- /dev/null +++ b/src/main/java/util/message/InputMessage.java @@ -0,0 +1,24 @@ +package util.message; + +import util.EnumUtil; + +public enum InputMessage implements EnumUtil { + + INPUT_POSSESSION_AMOUNT_MESSAGE("자판기가 보유하고 있는 금액을 입력해 주세요."); + + private final String message; + + InputMessage(final String message) { + this.message = message; + } + + @Override + public String getKey() { + return name(); + } + + @Override + public String getValue() { + return message; + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 000000000..9659de2da --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,31 @@ +package view; + +import util.exception.ConsoleException; +import util.exception.GlobalException; +import util.message.ExceptionMessage; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.function.Supplier; + +public class InputView { + private final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + + public String readConsole() { + try { + return bufferedReader.readLine(); + } catch (IOException e) { + throw new ConsoleException(String.format(ExceptionMessage.INPUT_MESSAGE.getValue(), e.getMessage())); + } + } + + public T getUserInput(Supplier inputReader) { + try { + return inputReader.get(); + } catch (GlobalException | IllegalArgumentException e) { + //OutputView.printMessage(e.getMessage()); + return getUserInput(inputReader); + } + } +} diff --git a/src/test/java/domain/wrapper/AmountTest.java b/src/test/java/domain/wrapper/AmountTest.java new file mode 100644 index 000000000..d1fa224dc --- /dev/null +++ b/src/test/java/domain/wrapper/AmountTest.java @@ -0,0 +1,43 @@ +package domain.wrapper; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static util.message.ExceptionMessage.BLANK_MESSAGE; +import static util.message.ExceptionMessage.TYPE_MESSAGE; + +public class AmountTest { + @ParameterizedTest + @DisplayName("보유금액을 올바르게 입력한 경우 예외가 발생하지 않는다.") + @CsvSource("450") + void givenNormalAmount_thenSuccess(final String amount) { + assertThat(Amount.create(amount)) + .isInstanceOf(Amount.class); + + assertThatCode(() -> Amount.create(amount)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest + @DisplayName("보유금액을 빈값으로 입력한 경우 예외가 발생한다.") + @ValueSource(strings = {"", " ", " ", " ", " ", "\n", "\t", "\r"}) + void givenBlankAmount_thenFail(final String amount) { + assertThatThrownBy(() -> Amount.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(BLANK_MESSAGE.getValue(), "보유금액")); + } + + @ParameterizedTest + @DisplayName("보유금액을 숫자가 아닌 형태로 입력한 경우 예외가 발생한다.") + @ValueSource(strings = {"abc", "12bd"}) + void givenNonNumeric_thenFail(final String amount) { + assertThatThrownBy(() -> Amount.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(TYPE_MESSAGE.getValue(), "보유금액")); + } +} From b365f486f2cc6c9a1307f78e6a7baf8e44588b61 Mon Sep 17 00:00:00 2001 From: suhyun Date: Sat, 25 Nov 2023 10:16:56 +0900 Subject: [PATCH 02/10] =?UTF-8?q?test:=20=EB=B3=B4=EC=9C=A0=EA=B8=88?= =?UTF-8?q?=EC=95=A1=EC=9D=84=20=EC=9E=85=EB=A0=A5=ED=95=A0=EB=95=8C=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/wrapper/Amount.java | 2 +- src/test/java/domain/wrapper/AmountTest.java | 22 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/domain/wrapper/Amount.java b/src/main/java/domain/wrapper/Amount.java index 113e5051d..94898a1b4 100644 --- a/src/main/java/domain/wrapper/Amount.java +++ b/src/main/java/domain/wrapper/Amount.java @@ -45,7 +45,7 @@ private int validateDivisibleBy10(final int amount){ } private int validateRange(final int amount) { - if (amount < AmountConstant.ZERO.getValue()) { + if (amount <= AmountConstant.ZERO.getValue()) { throw new IllegalArgumentException(String.format(RANGE_MESSAGE.getValue(), AmountConstant.ZERO.getValue())); } return amount; diff --git a/src/test/java/domain/wrapper/AmountTest.java b/src/test/java/domain/wrapper/AmountTest.java index d1fa224dc..1908b621e 100644 --- a/src/test/java/domain/wrapper/AmountTest.java +++ b/src/test/java/domain/wrapper/AmountTest.java @@ -1,5 +1,6 @@ package domain.wrapper; +import domain.constant.AmountConstant; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -8,8 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static util.message.ExceptionMessage.BLANK_MESSAGE; -import static util.message.ExceptionMessage.TYPE_MESSAGE; +import static util.message.ExceptionMessage.*; public class AmountTest { @ParameterizedTest @@ -40,4 +40,22 @@ void givenNonNumeric_thenFail(final String amount) { .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(String.format(TYPE_MESSAGE.getValue(), "보유금액")); } + + @ParameterizedTest + @DisplayName("보유금액이 10으로 나누어 떨어지지 않는 경우 예외가 발생한다.") + @ValueSource(strings = {"456", "123"}) + void givenNonDivisibleBy10_thenFail(final String amount) { + assertThatThrownBy(() -> Amount.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(TEN_UNIT_MESSAGE.getValue(), AmountConstant.ZERO.getValue())); + } + + @ParameterizedTest + @DisplayName("보유금액이 0이하인경우 예외가 발생한다.") + @ValueSource(strings = {"-1", "0"}) + void givenLessZero_thenFail(final String amount) { + assertThatThrownBy(() -> Amount.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(RANGE_MESSAGE.getValue(), AmountConstant.ZERO.getValue())); + } } From b200e79bdd2aa7b9a346bd5221468c34e7b8535a Mon Sep 17 00:00:00 2001 From: suhyun Date: Sat, 25 Nov 2023 15:10:27 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EB=B3=B4=EC=9C=A0=EA=B8=88?= =?UTF-8?q?=EC=95=A1=EC=9D=84=20=ED=86=A0=EB=8C=80=EB=A1=9C=20=EC=9E=90?= =?UTF-8?q?=ED=8C=90=EA=B8=B0=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 18 +++++- .../controller/VendingMachineController.java | 50 ++++++++++++++++ src/main/java/domain/Coin.java | 57 +++++++++++++++++++ src/main/java/domain/CoinCountGenerator.java | 16 ++++++ src/main/java/domain/VendingMachine.java | 28 +++++++++ .../java/dto/VendingMachineStatusDto.java | 23 ++++++++ .../java/service/VendingMachineService.java | 36 ++++++++++++ src/main/java/util/CoinGenerator.java | 7 +++ src/main/java/util/EnumUtil.java | 6 ++ .../java/util/exception/ConsoleException.java | 7 +++ .../java/util/exception/GlobalException.java | 7 +++ .../exception/NoMatchingCoinException.java | 7 +++ .../util/exception/NoResourceException.java | 7 +++ src/main/java/util/message/OutputMessage.java | 28 +++++++++ src/main/java/vendingmachine/Application.java | 5 +- src/main/java/vendingmachine/Coin.java | 16 ------ src/main/java/view/OutputView.java | 21 +++++++ .../service/VendingMachineServiceTest.java | 39 +++++++++++++ 18 files changed, 359 insertions(+), 19 deletions(-) create mode 100644 src/main/java/controller/VendingMachineController.java create mode 100644 src/main/java/domain/Coin.java create mode 100644 src/main/java/domain/CoinCountGenerator.java create mode 100644 src/main/java/domain/VendingMachine.java create mode 100644 src/main/java/dto/VendingMachineStatusDto.java create mode 100644 src/main/java/service/VendingMachineService.java create mode 100644 src/main/java/util/CoinGenerator.java create mode 100644 src/main/java/util/EnumUtil.java create mode 100644 src/main/java/util/exception/ConsoleException.java create mode 100644 src/main/java/util/exception/GlobalException.java create mode 100644 src/main/java/util/exception/NoMatchingCoinException.java create mode 100644 src/main/java/util/exception/NoResourceException.java create mode 100644 src/main/java/util/message/OutputMessage.java delete mode 100644 src/main/java/vendingmachine/Coin.java create mode 100644 src/main/java/view/OutputView.java create mode 100644 src/test/java/service/VendingMachineServiceTest.java diff --git a/docs/README.md b/docs/README.md index e5e0f623c..9f495bd05 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,20 @@ [ ✅ ] 사용자는 자판기가 보유하고 있는 금액을 입력한다. -[ ] 자판기가 보유하고 있는 금액으로 동전을 무작위로 생성한다. +[ ✅ ] 자판기가 보유하고 있는 금액으로 동전을 무작위로 생성한다. -- 투입금액으로 동전을 생성하지 않는다. == Coin클래스로 동전을 생성한다. \ No newline at end of file +- 투입금액으로 동전을 생성하지 않는다. == Coin클래스로 동전을 생성한다. + +[ ] 상품명, 가격, 수량을 입력하여 상품을 추가할 수 있다. + +- 상품 가격은 100원부터 시작하며, 10원으로 나누어떨어져야 한다. + +[ ] 사용자가 투입한 금액으로 상품을 구매할 수 있다. + +[ ] 잔돈을 돌려줄 때 현재 보유한 최소 개수의 동전으로 잔돈을 돌려준다. + +[ ] 남은 금액이 상품의 최저 가격보다 적거나, 모든 상품이 소진된 경우 바로 잔돈을 돌려준다. + +[ ] 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환한다. + +- 반환되지 않은 금액은 자판기에 남는다. \ No newline at end of file diff --git a/src/main/java/controller/VendingMachineController.java b/src/main/java/controller/VendingMachineController.java new file mode 100644 index 000000000..c8a6d75f6 --- /dev/null +++ b/src/main/java/controller/VendingMachineController.java @@ -0,0 +1,50 @@ +package controller; + +import domain.CoinCountGenerator; +import domain.PossesionAmount; +import domain.VendingMachine; +import dto.VendingMachineStatusDto; +import service.PossessionAmountService; +import service.VendingMachineService; +import view.InputView; +import view.OutputView; + +import java.util.List; + +import static util.message.InputMessage.INPUT_POSSESSION_AMOUNT_MESSAGE; +import static util.message.OutputMessage.VENDING_MACHINE_STATUS; + +public class VendingMachineController { + private final InputView inputView; + private final PossessionAmountService possesionAmountService; + private final VendingMachineService vendingMachineService; + + private final OutputView outputView; + + public VendingMachineController(){ + inputView = new InputView(); + possesionAmountService = new PossessionAmountService(); + VendingMachine vendingMachine = new VendingMachine(); + CoinCountGenerator coinCountGenerator = new CoinCountGenerator(); + vendingMachineService = new VendingMachineService(vendingMachine, coinCountGenerator); + outputView = new OutputView(); + } + + public void start(){ + String amount = getPossessionAmount(); + PossesionAmount possessionAmount = createPossessionAmount(amount); + List statusList = vendingMachineService.generateRandomCoins(possessionAmount.getAmount()); + outputView.printVendingMachineStatus(statusList); + } + + private String getPossessionAmount(){ + return inputView.getUserInput(() -> { + OutputView.printMessage(INPUT_POSSESSION_AMOUNT_MESSAGE.getValue()); + return inputView.readConsole(); + }); + } + + private PossesionAmount createPossessionAmount(String possessionAmount){ + return possesionAmountService.createPossessionAmount(possessionAmount); + } +} diff --git a/src/main/java/domain/Coin.java b/src/main/java/domain/Coin.java new file mode 100644 index 000000000..088cfd33e --- /dev/null +++ b/src/main/java/domain/Coin.java @@ -0,0 +1,57 @@ +package domain; + +import camp.nextstep.edu.missionutils.Randoms; +import util.exception.NoMatchingCoinException; +import util.message.ExceptionMessage; + +import java.util.Arrays; +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 from(int amount) { + try { + return Arrays.stream(Coin.values()) + .filter(coin -> coin.getAmount() == amount) + .findFirst() + .orElseThrow(() -> new NoMatchingCoinException(ExceptionMessage.NOT_COIN_MESSAGE.getValue())); + } catch (NoMatchingCoinException ex) { + throw new NoMatchingCoinException(String.format(ExceptionMessage.NOT_COIN_MESSAGE.getValue(), amount)); + } + } + + + public static Coin pickRandomWithLimit(int balanceLimit) { + Coin randomCoin = Coin.pickRandom(); + if (randomCoin.getAmount() > balanceLimit) { + return pickRandomWithLimit(balanceLimit); + } + + return randomCoin; + } + + private static Coin pickRandom() { + int pickedAmount = Randoms.pickNumberInList( + Arrays.stream(values()) + .map(Coin::getAmount) + .collect(Collectors.toList()) + ); + + return Coin.from(pickedAmount); + } + + public int getAmount() { + return amount; + } + +} diff --git a/src/main/java/domain/CoinCountGenerator.java b/src/main/java/domain/CoinCountGenerator.java new file mode 100644 index 000000000..3dc7010be --- /dev/null +++ b/src/main/java/domain/CoinCountGenerator.java @@ -0,0 +1,16 @@ +package domain; + +import util.CoinGenerator; + +public class CoinCountGenerator implements CoinGenerator { + + @Override + public int generateRandomCoins(Coin coin, int amount) { + int coinCount = 0; + while (amount >= coin.getAmount()) { + amount -= coin.getAmount(); + coinCount++; + } + return coinCount; + } +} diff --git a/src/main/java/domain/VendingMachine.java b/src/main/java/domain/VendingMachine.java new file mode 100644 index 000000000..10f3c281c --- /dev/null +++ b/src/main/java/domain/VendingMachine.java @@ -0,0 +1,28 @@ +package domain; + +import camp.nextstep.edu.missionutils.Randoms; +import dto.VendingMachineStatusDto; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VendingMachine { + private final HashMap vendingMachine; + + public VendingMachine(){ + vendingMachine = new HashMap<>(); + init(); + } + + private void init(){ + for (Coin coin : Coin.values()) { + vendingMachine.put(coin, 0); + } + } + + public void addCoins(Coin coin, int count) { + vendingMachine.put(coin, vendingMachine.get(coin) + count); + } +} diff --git a/src/main/java/dto/VendingMachineStatusDto.java b/src/main/java/dto/VendingMachineStatusDto.java new file mode 100644 index 000000000..9db254f87 --- /dev/null +++ b/src/main/java/dto/VendingMachineStatusDto.java @@ -0,0 +1,23 @@ +package dto; + +public class VendingMachineStatusDto { + private final int coin; + private final int count; + + private VendingMachineStatusDto(final int coin, final int count){ + this.coin = coin; + this.count = count; + } + + public static VendingMachineStatusDto create(final int coin, final int count) { + return new VendingMachineStatusDto(coin, count); + } + + public int getCount(){ + return count; + } + + public int getCoin(){ + return coin; + } +} diff --git a/src/main/java/service/VendingMachineService.java b/src/main/java/service/VendingMachineService.java new file mode 100644 index 000000000..1dddd1797 --- /dev/null +++ b/src/main/java/service/VendingMachineService.java @@ -0,0 +1,36 @@ +package service; + +import domain.Coin; +import domain.CoinCountGenerator; +import domain.VendingMachine; +import dto.VendingMachineStatusDto; + +import java.util.ArrayList; +import java.util.List; + +public class VendingMachineService { + + private final VendingMachine vendingMachine; + private final CoinCountGenerator coinCountGenerator; + + public VendingMachineService(final VendingMachine vendingMachine, final CoinCountGenerator coinCountGenerator) { + this.vendingMachine = vendingMachine; + this.coinCountGenerator = coinCountGenerator; + } + + public List generateRandomCoins(int amount) { + List statusList = new ArrayList<>(); + + for (Coin coin : Coin.values()) { + CoinCountGenerator coinCountGenerator = new CoinCountGenerator(); + int coinCount = coinCountGenerator.generateRandomCoins(coin, amount); + vendingMachine.addCoins(coin, coinCount); + statusList.add(VendingMachineStatusDto.create(coin.getAmount(), coinCount)); + amount -= coin.getAmount() * coinCount; + } + + return statusList; + } + + +} diff --git a/src/main/java/util/CoinGenerator.java b/src/main/java/util/CoinGenerator.java new file mode 100644 index 000000000..e4869117d --- /dev/null +++ b/src/main/java/util/CoinGenerator.java @@ -0,0 +1,7 @@ +package util; + +import domain.Coin; + +public interface CoinGenerator { + int generateRandomCoins(Coin coin, int amount); +} diff --git a/src/main/java/util/EnumUtil.java b/src/main/java/util/EnumUtil.java new file mode 100644 index 000000000..e0c0bcb60 --- /dev/null +++ b/src/main/java/util/EnumUtil.java @@ -0,0 +1,6 @@ +package util; + +public interface EnumUtil { + T1 getKey(); + T2 getValue(); +} diff --git a/src/main/java/util/exception/ConsoleException.java b/src/main/java/util/exception/ConsoleException.java new file mode 100644 index 000000000..2c43267b1 --- /dev/null +++ b/src/main/java/util/exception/ConsoleException.java @@ -0,0 +1,7 @@ +package util.exception; + +public class ConsoleException extends GlobalException{ + public ConsoleException(final String message) { + super(message); + } +} diff --git a/src/main/java/util/exception/GlobalException.java b/src/main/java/util/exception/GlobalException.java new file mode 100644 index 000000000..12ab0f809 --- /dev/null +++ b/src/main/java/util/exception/GlobalException.java @@ -0,0 +1,7 @@ +package util.exception; + +public class GlobalException extends RuntimeException{ + public GlobalException(final String message) { + super(message); + } +} diff --git a/src/main/java/util/exception/NoMatchingCoinException.java b/src/main/java/util/exception/NoMatchingCoinException.java new file mode 100644 index 000000000..4a50214fc --- /dev/null +++ b/src/main/java/util/exception/NoMatchingCoinException.java @@ -0,0 +1,7 @@ +package util.exception; + +public class NoMatchingCoinException extends GlobalException{ + public NoMatchingCoinException(final String message) { + super(message); + } +} diff --git a/src/main/java/util/exception/NoResourceException.java b/src/main/java/util/exception/NoResourceException.java new file mode 100644 index 000000000..2f9ec9df2 --- /dev/null +++ b/src/main/java/util/exception/NoResourceException.java @@ -0,0 +1,7 @@ +package util.exception; + +public class NoResourceException extends GlobalException{ + public NoResourceException(String message){ + super(message); + } +} diff --git a/src/main/java/util/message/OutputMessage.java b/src/main/java/util/message/OutputMessage.java new file mode 100644 index 000000000..43f58ab74 --- /dev/null +++ b/src/main/java/util/message/OutputMessage.java @@ -0,0 +1,28 @@ +package util.message; + +import util.EnumUtil; + +public enum OutputMessage implements EnumUtil { + + WON("원"), + COUNT("개"), + HYPHEN(" - "), + VENDING_MACHINE_STATUS("자판기가 보유한 동전\n"); + + + private final String message; + + OutputMessage(final String message) { + this.message = message; + } + + @Override + public String getKey() { + return name(); + } + + @Override + public String getValue() { + return message; + } +} diff --git a/src/main/java/vendingmachine/Application.java b/src/main/java/vendingmachine/Application.java index 9d3be447b..500fb4834 100644 --- a/src/main/java/vendingmachine/Application.java +++ b/src/main/java/vendingmachine/Application.java @@ -1,7 +1,10 @@ package vendingmachine; +import controller.VendingMachineController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + VendingMachineController vendingMachineController = new VendingMachineController(); + vendingMachineController.start(); } } diff --git a/src/main/java/vendingmachine/Coin.java b/src/main/java/vendingmachine/Coin.java deleted file mode 100644 index c76293fbc..000000000 --- a/src/main/java/vendingmachine/Coin.java +++ /dev/null @@ -1,16 +0,0 @@ -package vendingmachine; - -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; - } - - // 추가 기능 구현 -} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 000000000..3a526673c --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,21 @@ +package view; + +import dto.VendingMachineStatusDto; +import util.message.OutputMessage; + +import java.util.List; + +import static util.message.OutputMessage.*; + +public class OutputView { + public static void printMessage(String message) { + System.out.println(message); + } + + public void printVendingMachineStatus(List statusList) { + System.out.println(VENDING_MACHINE_STATUS.getValue()); + for (VendingMachineStatusDto statusDto : statusList) { + System.out.println(String.format("%d" + WON.getValue() + HYPHEN.getValue() + "%d" + COUNT.getValue(), statusDto.getCoin(), statusDto.getCount())); + } + } +} diff --git a/src/test/java/service/VendingMachineServiceTest.java b/src/test/java/service/VendingMachineServiceTest.java new file mode 100644 index 000000000..f925d95f9 --- /dev/null +++ b/src/test/java/service/VendingMachineServiceTest.java @@ -0,0 +1,39 @@ +package service; + +import domain.Coin; +import domain.CoinCountGenerator; +import domain.VendingMachine; +import dto.VendingMachineStatusDto; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.*; + +public class VendingMachineServiceTest { + + @Test + void generateRandomCoins_ShouldGenerateRandomCoins() { + VendingMachine vendingMachine = new VendingMachine(); + CoinCountGenerator coinCountGenerator = new CoinCountGenerator(); + VendingMachineService vendingMachineService = new VendingMachineService(vendingMachine, coinCountGenerator); + + int userAmount = 450; + List result = vendingMachineService.generateRandomCoins(userAmount); + + assertEquals(4, result.size()); + + int totalAmount = 0; + for (VendingMachineStatusDto status : result) { + totalAmount += status.getCoin() * status.getCount(); + } + + // 생성된 동전의 총 금액이 사용자가 입력한 금액과 일치하는지 확인 + assertEquals(userAmount, totalAmount); + } + + +} From a1cd5f54ff17f35dbbae192f0b96d2815730a67d Mon Sep 17 00:00:00 2001 From: suhyun Date: Sat, 25 Nov 2023 21:35:29 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=EB=AA=85,=20?= =?UTF-8?q?=EA=B0=80=EA=B2=A9,=20=EC=88=98=EB=9F=89=EC=9D=84=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 2 +- .../controller/VendingMachineController.java | 9 +++ src/main/java/domain/Product.java | 65 +++++++++++++++++ src/main/java/domain/Products.java | 73 +++++++++++++++++++ .../{AmountConstant.java => Constant.java} | 5 +- .../java/domain/constant/ProductConstant.java | 23 ++++++ .../domain/constant/ProductsConstant.java | 24 ++++++ src/main/java/domain/wrapper/Amount.java | 10 +-- src/main/java/domain/wrapper/Name.java | 41 +++++++++++ src/main/java/domain/wrapper/Price.java | 70 ++++++++++++++++++ src/main/java/domain/wrapper/Quantity.java | 63 ++++++++++++++++ .../util/exception/DuplicateException.java | 7 ++ .../util/exception/NoResourceException.java | 7 -- .../java/util/message/ExceptionMessage.java | 2 +- src/main/java/util/message/InputMessage.java | 3 +- src/main/java/view/InputView.java | 2 +- src/test/java/domain/ProductsTest.java | 70 ++++++++++++++++++ src/test/java/domain/wrapper/AmountTest.java | 6 +- .../java/domain/wrapper/ProductNameTest.java | 33 +++++++++ .../java/domain/wrapper/ProductPriceTest.java | 62 ++++++++++++++++ .../domain/wrapper/ProductQuantityTest.java | 53 ++++++++++++++ src/test/java/provider/TestProvider.java | 12 +++ 22 files changed, 621 insertions(+), 21 deletions(-) create mode 100644 src/main/java/domain/Product.java create mode 100644 src/main/java/domain/Products.java rename src/main/java/domain/constant/{AmountConstant.java => Constant.java} (72%) create mode 100644 src/main/java/domain/constant/ProductConstant.java create mode 100644 src/main/java/domain/constant/ProductsConstant.java create mode 100644 src/main/java/domain/wrapper/Name.java create mode 100644 src/main/java/domain/wrapper/Price.java create mode 100644 src/main/java/domain/wrapper/Quantity.java create mode 100644 src/main/java/util/exception/DuplicateException.java delete mode 100644 src/main/java/util/exception/NoResourceException.java create mode 100644 src/test/java/domain/ProductsTest.java create mode 100644 src/test/java/domain/wrapper/ProductNameTest.java create mode 100644 src/test/java/domain/wrapper/ProductPriceTest.java create mode 100644 src/test/java/domain/wrapper/ProductQuantityTest.java create mode 100644 src/test/java/provider/TestProvider.java diff --git a/docs/README.md b/docs/README.md index 9f495bd05..027b871ca 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,7 +6,7 @@ - 투입금액으로 동전을 생성하지 않는다. == Coin클래스로 동전을 생성한다. -[ ] 상품명, 가격, 수량을 입력하여 상품을 추가할 수 있다. +[ ✅ ] 상품명, 가격, 수량을 입력하여 상품을 추가할 수 있다. - 상품 가격은 100원부터 시작하며, 10원으로 나누어떨어져야 한다. diff --git a/src/main/java/controller/VendingMachineController.java b/src/main/java/controller/VendingMachineController.java index c8a6d75f6..4740241ea 100644 --- a/src/main/java/controller/VendingMachineController.java +++ b/src/main/java/controller/VendingMachineController.java @@ -12,6 +12,7 @@ import java.util.List; import static util.message.InputMessage.INPUT_POSSESSION_AMOUNT_MESSAGE; +import static util.message.InputMessage.INPUT_PRODUCT_DETAIL; import static util.message.OutputMessage.VENDING_MACHINE_STATUS; public class VendingMachineController { @@ -35,6 +36,7 @@ public void start(){ PossesionAmount possessionAmount = createPossessionAmount(amount); List statusList = vendingMachineService.generateRandomCoins(possessionAmount.getAmount()); outputView.printVendingMachineStatus(statusList); + String productInfo = getProductInfo(); } private String getPossessionAmount(){ @@ -47,4 +49,11 @@ private String getPossessionAmount(){ private PossesionAmount createPossessionAmount(String possessionAmount){ return possesionAmountService.createPossessionAmount(possessionAmount); } + + private String getProductInfo(){ + return inputView.getUserInput(() -> { + OutputView.printMessage(INPUT_PRODUCT_DETAIL.getValue()); + return inputView.readConsole(); + }); + } } diff --git a/src/main/java/domain/Product.java b/src/main/java/domain/Product.java new file mode 100644 index 000000000..dbf0b55d2 --- /dev/null +++ b/src/main/java/domain/Product.java @@ -0,0 +1,65 @@ +package domain; + +import domain.wrapper.Name; +import domain.wrapper.Price; +import domain.wrapper.Quantity; + +import java.util.Objects; + +import static domain.constant.ProductConstant.SPLIT_DELIMITER_COMMA; +import static util.message.ExceptionMessage.BLANK_MESSAGE; + +public class Product { + private final Name name; + private final Price price; + private final Quantity quantity; + + private Product(final String productDetail){ + validateBlank(productDetail); + String[] product = splitProduct(productDetail); + this.name = Name.create(product[0]); + this.price = Price.create(product[1]); + this.quantity = Quantity.create(product[2]); + } + + public static Product create(final String productDetail){ + return new Product(productDetail); + } + + private String[] splitProduct(final String productDetail){ + return productDetail.split(SPLIT_DELIMITER_COMMA.getValue()); + } + + private void validateBlank(final String productDetail){ + if (productDetail == null || productDetail.trim().isEmpty()) { + throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "상품명, 가격, 수량")); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Product product = (Product) o; + return Objects.equals(name, product.name) && + Objects.equals(price, product.price) && + Objects.equals(quantity, product.quantity); + } + + @Override + public int hashCode() { + return Objects.hash(name, price, quantity); + } + + public String getName() { + return name.getName(); + } + + public int getPrice() { + return price.getPrice(); + } + + public int getQuantity() { + return quantity.getQuantity(); + } +} diff --git a/src/main/java/domain/Products.java b/src/main/java/domain/Products.java new file mode 100644 index 000000000..7c5af143b --- /dev/null +++ b/src/main/java/domain/Products.java @@ -0,0 +1,73 @@ +package domain; + +import util.exception.DuplicateException; + +import java.util.*; +import java.util.stream.Collectors; + +import static domain.constant.ProductsConstant.*; +import static util.message.ExceptionMessage.BLANK_MESSAGE; +import static util.message.ExceptionMessage.DUPLICATE_MESSAGE; + +public class Products { + private final List products; + + public Products(final String productsInfo){ + validateBlank(productsInfo); + this.products = create(productsInfo); + validateDuplicateProducts(); + validateDuplicateProductName(); + } + + private void validateBlank(final String productsInfo){ + if (productsInfo == null || productsInfo.trim().isEmpty()) { + throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "상품정보")); + } + } + + private List create(final String productsInfo){ + String[] product = splitProducts(productsInfo); + return Arrays.stream(product) + .map(Product::create) + .collect(Collectors.toList()); + } + + private String[] splitProducts(final String productsInfo) { + String[] product = productsInfo.split(SPLIT_DELIMITER_SEMICOLON.getValue()); + for (int i = 0; i < product.length; i++) { + product[i] = product[i].trim().replaceAll("\\[|\\]", BLANK.getValue()); + } + return product; + } + + private void validateDuplicateProducts() { + int uniqueCarCount = new HashSet<>(products).size(); + if (products.size() != uniqueCarCount) { + throw new DuplicateException(String.format(DUPLICATE_MESSAGE.getValue(), "상품정보")); + } + } + + private void validateDuplicateProductName(){ + Set productNames = new HashSet<>(); + for (Product product : products) { + String productName = product.getName().toString(); + if (!productNames.add(productName)) { + throw new DuplicateException(String.format(DUPLICATE_MESSAGE.getValue(), "상품명")); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Products products1 = (Products) o; + return Objects.equals(products, products1.products); + } + + @Override + public int hashCode() { + return Objects.hash(products); + } + +} diff --git a/src/main/java/domain/constant/AmountConstant.java b/src/main/java/domain/constant/Constant.java similarity index 72% rename from src/main/java/domain/constant/AmountConstant.java rename to src/main/java/domain/constant/Constant.java index d095d0688..11622e082 100644 --- a/src/main/java/domain/constant/AmountConstant.java +++ b/src/main/java/domain/constant/Constant.java @@ -2,13 +2,14 @@ import util.EnumUtil; -public enum AmountConstant implements EnumUtil { +public enum Constant implements EnumUtil { COIN_TEN(10), + COIN_HUNDRED(100), ZERO(0); private final int number; - AmountConstant(final int number){ + Constant(final int number){ this.number = number; } diff --git a/src/main/java/domain/constant/ProductConstant.java b/src/main/java/domain/constant/ProductConstant.java new file mode 100644 index 000000000..84d24070c --- /dev/null +++ b/src/main/java/domain/constant/ProductConstant.java @@ -0,0 +1,23 @@ +package domain.constant; + +import util.EnumUtil; + +public enum ProductConstant implements EnumUtil { + SPLIT_DELIMITER_COMMA(","); + + private final String value; + + ProductConstant(final String value) { + this.value = value; + } + + @Override + public String getKey() { + return name(); + } + + @Override + public String getValue() { + return value; + } +} diff --git a/src/main/java/domain/constant/ProductsConstant.java b/src/main/java/domain/constant/ProductsConstant.java new file mode 100644 index 000000000..5ab0d28a2 --- /dev/null +++ b/src/main/java/domain/constant/ProductsConstant.java @@ -0,0 +1,24 @@ +package domain.constant; + +import util.EnumUtil; + +public enum ProductsConstant implements EnumUtil { + SPLIT_DELIMITER_SEMICOLON(";"), + BLANK(""); + + private final String value; + + ProductsConstant(final String value) { + this.value = value; + } + + @Override + public String getKey() { + return name(); + } + + @Override + public String getValue() { + return value; + } +} diff --git a/src/main/java/domain/wrapper/Amount.java b/src/main/java/domain/wrapper/Amount.java index 94898a1b4..3fec20734 100644 --- a/src/main/java/domain/wrapper/Amount.java +++ b/src/main/java/domain/wrapper/Amount.java @@ -1,6 +1,6 @@ package domain.wrapper; -import domain.constant.AmountConstant; +import domain.constant.Constant; import static util.message.ExceptionMessage.*; @@ -38,15 +38,15 @@ private int validateType(final String amount) { } private int validateDivisibleBy10(final int amount){ - if(amount % AmountConstant.COIN_TEN.getValue() != AmountConstant.ZERO.getValue()){ - throw new IllegalArgumentException(String.format(TEN_UNIT_MESSAGE.getValue(), AmountConstant.ZERO.getValue())); + if(amount % Constant.COIN_TEN.getValue() != Constant.ZERO.getValue()){ + throw new IllegalArgumentException(String.format(TEN_UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); } return amount; } private int validateRange(final int amount) { - if (amount <= AmountConstant.ZERO.getValue()) { - throw new IllegalArgumentException(String.format(RANGE_MESSAGE.getValue(), AmountConstant.ZERO.getValue())); + if (amount <= Constant.ZERO.getValue()) { + throw new IllegalArgumentException(String.format(RANGE_MESSAGE.getValue(), Constant.ZERO.getValue())); } return amount; } diff --git a/src/main/java/domain/wrapper/Name.java b/src/main/java/domain/wrapper/Name.java new file mode 100644 index 000000000..5e4ee4923 --- /dev/null +++ b/src/main/java/domain/wrapper/Name.java @@ -0,0 +1,41 @@ +package domain.wrapper; + +import java.util.Objects; + +import static util.message.ExceptionMessage.BLANK_MESSAGE; + +public class Name { + private final String name; + + private Name(final String name){ + validateBlank(name); + this.name = name; + } + + public static Name create(final String name){ + return new Name(name); + } + + private void validateBlank(final String productDetail){ + if (productDetail == null || productDetail.trim().isEmpty()) { + throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "상품명")); + } + } + + @Override + public boolean equals(Object diffName) { + if (this == diffName) return true; + if (diffName == null || getClass() != diffName.getClass()) return false; + Name nameInfo = (Name) diffName; + return Objects.equals(name, nameInfo.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/domain/wrapper/Price.java b/src/main/java/domain/wrapper/Price.java new file mode 100644 index 000000000..e87c532cb --- /dev/null +++ b/src/main/java/domain/wrapper/Price.java @@ -0,0 +1,70 @@ +package domain.wrapper; + +import domain.constant.Constant; + +import java.util.Objects; + +import static util.message.ExceptionMessage.*; + +public class Price { + private final int price; + + private Price(final String priceInfo){ + validateBlank(priceInfo); + int amount = validateType(priceInfo); + amount = validateRange(amount); + amount = validateDivisibleBy10(amount); + this.price = amount; + } + + public static Price create(final String priceInfo){ + return new Price(priceInfo); + } + + private void validateBlank(final String productDetail){ + if (productDetail == null || productDetail.trim().isEmpty()) { + throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "가격")); + } + } + + private int validateType(final String priceInfo) { + int amount; + try { + amount = Integer.parseInt(priceInfo); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format(TYPE_MESSAGE.getValue(), "가격")); + } + return amount; + } + + private int validateRange(final int amount) { + if (amount < Constant.COIN_HUNDRED.getValue()) { + throw new IllegalArgumentException(String.format(RANGE_MESSAGE.getValue(), Constant.COIN_HUNDRED.getValue())); + } + return amount; + } + + private int validateDivisibleBy10(final int amount){ + if(amount % Constant.COIN_TEN.getValue() != Constant.ZERO.getValue()){ + throw new IllegalArgumentException(String.format(TEN_UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); + } + return amount; + } + + @Override + public boolean equals(Object diffPrice) { + if (this == diffPrice) return true; + if (diffPrice == null || getClass() != diffPrice.getClass()) return false; + Price priceInfo = (Price) diffPrice; + return Objects.equals(price, priceInfo.price); + } + + @Override + public int hashCode() { + return Objects.hash(price); + } + + public int getPrice() { + return price; + } +} diff --git a/src/main/java/domain/wrapper/Quantity.java b/src/main/java/domain/wrapper/Quantity.java new file mode 100644 index 000000000..066bf0684 --- /dev/null +++ b/src/main/java/domain/wrapper/Quantity.java @@ -0,0 +1,63 @@ +package domain.wrapper; + +import domain.constant.Constant; + +import java.util.Objects; + +import static util.message.ExceptionMessage.*; +import static util.message.ExceptionMessage.TEN_UNIT_MESSAGE; + +public class Quantity { + private final int quantity; + + private Quantity(final String quantityInfo){ + validateBlank(quantityInfo); + int amount = validateType(quantityInfo); + amount = validateRange(amount); + this.quantity = amount; + } + + public static Quantity create(final String quantityInfo){ + return new Quantity(quantityInfo); + } + + private void validateBlank(final String productDetail){ + if (productDetail == null || productDetail.trim().isEmpty()) { + throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "수량")); + } + } + + private int validateType(final String priceInfo) { + int amount; + try { + amount = Integer.parseInt(priceInfo); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format(TYPE_MESSAGE.getValue(), "수량")); + } + return amount; + } + + private int validateRange(final int amount) { + if (amount <= Constant.ZERO.getValue()) { + throw new IllegalArgumentException(String.format(RANGE_MESSAGE.getValue(), Constant.ZERO.getValue())); + } + return amount; + } + + @Override + public boolean equals(Object diffQuantity) { + if (this == diffQuantity) return true; + if (diffQuantity == null || getClass() != diffQuantity.getClass()) return false; + Quantity quantityInfo = (Quantity) diffQuantity; + return Objects.equals(quantity, quantityInfo.quantity); + } + + @Override + public int hashCode() { + return Objects.hash(quantity); + } + + public int getQuantity() { + return quantity; + } +} diff --git a/src/main/java/util/exception/DuplicateException.java b/src/main/java/util/exception/DuplicateException.java new file mode 100644 index 000000000..d5929500f --- /dev/null +++ b/src/main/java/util/exception/DuplicateException.java @@ -0,0 +1,7 @@ +package util.exception; + +public class DuplicateException extends GlobalException{ + public DuplicateException(final String message) { + super(message); + } +} diff --git a/src/main/java/util/exception/NoResourceException.java b/src/main/java/util/exception/NoResourceException.java deleted file mode 100644 index 2f9ec9df2..000000000 --- a/src/main/java/util/exception/NoResourceException.java +++ /dev/null @@ -1,7 +0,0 @@ -package util.exception; - -public class NoResourceException extends GlobalException{ - public NoResourceException(String message){ - super(message); - } -} diff --git a/src/main/java/util/message/ExceptionMessage.java b/src/main/java/util/message/ExceptionMessage.java index a10f8c4d6..d56573e18 100644 --- a/src/main/java/util/message/ExceptionMessage.java +++ b/src/main/java/util/message/ExceptionMessage.java @@ -9,7 +9,7 @@ public enum ExceptionMessage implements EnumUtil { , TYPE_MESSAGE("%s은(는) 숫자만 입력할 수 있습니다.") , RANGE_MESSAGE("%d 보다 큰 값을 입력해 주세요.") , TEN_UNIT_MESSAGE("%d원 단위로 입력해 주세요.") - , DUPLICATE_MESSAGE("중복된 값을 입력할 수 없습니다.") + , DUPLICATE_MESSAGE("%s을(를) 중복으로 입력할 수 없습니다.") , NO_RESOURCE_MESSAGE("%s(이)가 존재하지 않습니다.") , NOT_COIN_MESSAGE("해당하는 %d원 동전이 존재하지 않습니다."); private static final String ERROR_TAG = "[ERROR] "; diff --git a/src/main/java/util/message/InputMessage.java b/src/main/java/util/message/InputMessage.java index 112b78221..f6b8565aa 100644 --- a/src/main/java/util/message/InputMessage.java +++ b/src/main/java/util/message/InputMessage.java @@ -4,7 +4,8 @@ public enum InputMessage implements EnumUtil { - INPUT_POSSESSION_AMOUNT_MESSAGE("자판기가 보유하고 있는 금액을 입력해 주세요."); + INPUT_POSSESSION_AMOUNT_MESSAGE("자판기가 보유하고 있는 금액을 입력해 주세요."), + INPUT_PRODUCT_DETAIL("\n상품명과 가격, 수량을 입력해 주세요."); private final String message; diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 9659de2da..65ff9d2c9 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -24,7 +24,7 @@ public T getUserInput(Supplier inputReader) { try { return inputReader.get(); } catch (GlobalException | IllegalArgumentException e) { - //OutputView.printMessage(e.getMessage()); + OutputView.printMessage(e.getMessage()); return getUserInput(inputReader); } } diff --git a/src/test/java/domain/ProductsTest.java b/src/test/java/domain/ProductsTest.java new file mode 100644 index 000000000..945a4d9d3 --- /dev/null +++ b/src/test/java/domain/ProductsTest.java @@ -0,0 +1,70 @@ +package domain; + +import domain.Products; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; +import provider.TestProvider; +import util.exception.DuplicateException; +import util.exception.GlobalException; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static util.message.ExceptionMessage.BLANK_MESSAGE; +import static util.message.ExceptionMessage.DUPLICATE_MESSAGE; + +public class ProductsTest { + private Products testProducts; + + @BeforeEach + void init() { + String testProductInfos = "[콜라,1500,20];[사이다,1000,10]"; + testProducts = TestProvider.createTestProducts(testProductInfos); + } + + @ParameterizedTest + @ValueSource(strings = {"[콜라,1500,20];[사이다,1000,10]"}) + @DisplayName("정상적인 상품정보를 입력하면, 예외가 발생하지 않는다.") + void givenNormalProductInfos_thenSuccess(final String testProductInfos) { + // when & then + assertThatCode(() -> new Products(testProductInfos)) + .doesNotThrowAnyException(); + + assertThat(new Products(testProductInfos)) + .isEqualTo(testProducts); + } + + @ParameterizedTest + @NullSource + @DisplayName("상품정보에 null 값이 들어오면 split 시 예외가 발생한다.") + void givenNullProductInfos_thenFail(final String testProductInfos) { + assertThatThrownBy(() -> new Products(testProductInfos)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(BLANK_MESSAGE.getValue(), "상품정보")); + } + + @ParameterizedTest + @ValueSource(strings = {"[콜라,1500,20];[콜라,1500,20]"}) + @DisplayName("상품정보에 중복값이 들어오면 예외가 발생한다.") + void givenDuplicateProducts_thenFail(final String testProductInfos) { + assertThatThrownBy(() -> new Products(testProductInfos)) + .isInstanceOf(GlobalException.class) + .isExactlyInstanceOf(DuplicateException.class) + .hasMessageContaining(String.format(DUPLICATE_MESSAGE.getValue(), "상품정보")); + } + + @ParameterizedTest + @ValueSource(strings = {"[콜라,1500,20];[콜라,1500,10]"}) + @DisplayName("상품명에 중복값이 들어오면 예외가 발생한다.") + void givenDuplicateProductName_thenFail(final String testProductInfos) { + assertThatThrownBy(() -> new Products(testProductInfos)) + .isInstanceOf(GlobalException.class) + .isExactlyInstanceOf(DuplicateException.class) + .hasMessageContaining(String.format(DUPLICATE_MESSAGE.getValue(), "상품명")); + } +} diff --git a/src/test/java/domain/wrapper/AmountTest.java b/src/test/java/domain/wrapper/AmountTest.java index 1908b621e..3e086804e 100644 --- a/src/test/java/domain/wrapper/AmountTest.java +++ b/src/test/java/domain/wrapper/AmountTest.java @@ -1,6 +1,6 @@ package domain.wrapper; -import domain.constant.AmountConstant; +import domain.constant.Constant; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -47,7 +47,7 @@ void givenNonNumeric_thenFail(final String amount) { void givenNonDivisibleBy10_thenFail(final String amount) { assertThatThrownBy(() -> Amount.create(amount)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(String.format(TEN_UNIT_MESSAGE.getValue(), AmountConstant.ZERO.getValue())); + .hasMessageContaining(String.format(TEN_UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); } @ParameterizedTest @@ -56,6 +56,6 @@ void givenNonDivisibleBy10_thenFail(final String amount) { void givenLessZero_thenFail(final String amount) { assertThatThrownBy(() -> Amount.create(amount)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(String.format(RANGE_MESSAGE.getValue(), AmountConstant.ZERO.getValue())); + .hasMessageContaining(String.format(RANGE_MESSAGE.getValue(), Constant.ZERO.getValue())); } } diff --git a/src/test/java/domain/wrapper/ProductNameTest.java b/src/test/java/domain/wrapper/ProductNameTest.java new file mode 100644 index 000000000..270b20e6c --- /dev/null +++ b/src/test/java/domain/wrapper/ProductNameTest.java @@ -0,0 +1,33 @@ +package domain.wrapper; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static util.message.ExceptionMessage.BLANK_MESSAGE; + +public class ProductNameTest { + @ParameterizedTest + @DisplayName("정상적으로 입력했을 경우 예외를 처리하지 않는다.") + @ValueSource(strings = {"콜라", "사이다", "피자", "치킨"}) + void givenNormalName_thenSuccess(final String carName) { + assertThat(Name.create(carName)) + .isInstanceOf(Name.class); + + assertThatCode(() -> Name.create(carName)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest + @DisplayName("이름이 공백일 경우 예외를 처리한다.") + @EmptySource + void givenBlankName_thenFail(final String carName) { + assertThatThrownBy(() -> Name.create(carName)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(BLANK_MESSAGE.getValue(), "상품명")); + } +} diff --git a/src/test/java/domain/wrapper/ProductPriceTest.java b/src/test/java/domain/wrapper/ProductPriceTest.java new file mode 100644 index 000000000..52adf6b5d --- /dev/null +++ b/src/test/java/domain/wrapper/ProductPriceTest.java @@ -0,0 +1,62 @@ +package domain.wrapper; + +import domain.constant.Constant; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static util.message.ExceptionMessage.*; +import static util.message.ExceptionMessage.RANGE_MESSAGE; + +public class ProductPriceTest { + @ParameterizedTest + @DisplayName("상품가격을 올바르게 입력한 경우 예외가 발생하지 않는다.") + @CsvSource("450") + void givenNormalPrice_thenSuccess(final String amount) { + assertThat(Price.create(amount)) + .isInstanceOf(Price.class); + + assertThatCode(() -> Price.create(amount)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest + @DisplayName("상품가격을 빈값으로 입력한 경우 예외가 발생한다.") + @ValueSource(strings = {"", " ", " ", " ", " ", "\n", "\t", "\r"}) + void givenBlankPrice_thenFail(final String amount) { + assertThatThrownBy(() -> Price.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(BLANK_MESSAGE.getValue(), "가격")); + } + + @ParameterizedTest + @DisplayName("상품가격을 숫자가 아닌 형태로 입력한 경우 예외가 발생한다.") + @ValueSource(strings = {"abc", "12bd"}) + void givenNonNumeric_thenFail(final String amount) { + assertThatThrownBy(() -> Price.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(TYPE_MESSAGE.getValue(), "가격")); + } + + @ParameterizedTest + @DisplayName("가격이 100원보다 낮은경우 예외가 발생한다.") + @ValueSource(strings = {"-1", "80"}) + void givenLessZero_thenFail(final String amount) { + assertThatThrownBy(() -> Price.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(RANGE_MESSAGE.getValue(), Constant.COIN_HUNDRED.getValue())); + } + + @ParameterizedTest + @DisplayName("가격이 10으로 나누어 떨어지지 않는 경우 예외가 발생한다.") + @ValueSource(strings = {"456", "123"}) + void givenNonDivisibleBy10_thenFail(final String amount) { + assertThatThrownBy(() -> Price.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(TEN_UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); + } +} diff --git a/src/test/java/domain/wrapper/ProductQuantityTest.java b/src/test/java/domain/wrapper/ProductQuantityTest.java new file mode 100644 index 000000000..f99355100 --- /dev/null +++ b/src/test/java/domain/wrapper/ProductQuantityTest.java @@ -0,0 +1,53 @@ +package domain.wrapper; + +import domain.constant.Constant; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static util.message.ExceptionMessage.*; +import static util.message.ExceptionMessage.TEN_UNIT_MESSAGE; + +public class ProductQuantityTest { + @ParameterizedTest + @DisplayName("상품수량을 올바르게 입력한 경우 예외가 발생하지 않는다.") + @CsvSource("450") + void givenNormalQuantity_thenSuccess(final String amount) { + assertThat(Quantity.create(amount)) + .isInstanceOf(Quantity.class); + + assertThatCode(() -> Quantity.create(amount)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest + @DisplayName("상품수량을 빈값으로 입력한 경우 예외가 발생한다.") + @ValueSource(strings = {"", " ", " ", " ", " ", "\n", "\t", "\r"}) + void givenBlankQuantity_thenFail(final String amount) { + assertThatThrownBy(() -> Quantity.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(BLANK_MESSAGE.getValue(), "수량")); + } + + @ParameterizedTest + @DisplayName("상품수량을 숫자가 아닌 형태로 입력한 경우 예외가 발생한다.") + @ValueSource(strings = {"abc", "12bd"}) + void givenNonNumeric_thenFail(final String amount) { + assertThatThrownBy(() -> Quantity.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(TYPE_MESSAGE.getValue(), "수량")); + } + + @ParameterizedTest + @DisplayName("수량이 0이하인경우 예외가 발생한다.") + @ValueSource(strings = {"-1", "0"}) + void givenLessZero_thenFail(final String amount) { + assertThatThrownBy(() -> Quantity.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(RANGE_MESSAGE.getValue(), Constant.ZERO.getValue())); + } +} diff --git a/src/test/java/provider/TestProvider.java b/src/test/java/provider/TestProvider.java new file mode 100644 index 000000000..516294dcd --- /dev/null +++ b/src/test/java/provider/TestProvider.java @@ -0,0 +1,12 @@ +package provider; + +import domain.Product; +import domain.Products; + +import java.util.List; + +public class TestProvider { + public static Products createTestProducts(final String productsInfo) { + return new Products(productsInfo); + } +} From c5cd6d042ac455aa9190c7c5462abc2f4afb5e28 Mon Sep 17 00:00:00 2001 From: suhyun Date: Sat, 25 Nov 2023 21:45:53 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=EA=B0=81=20=EC=83=81=ED=92=88?= =?UTF-8?q?=EB=AA=85,=20=EA=B0=80=EA=B2=A9,=20=EC=88=98=EB=9F=89=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC,=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/VendingMachineController.java | 9 +++++++++ src/main/java/service/ProductsService.java | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/main/java/service/ProductsService.java diff --git a/src/main/java/controller/VendingMachineController.java b/src/main/java/controller/VendingMachineController.java index 4740241ea..dab3b7361 100644 --- a/src/main/java/controller/VendingMachineController.java +++ b/src/main/java/controller/VendingMachineController.java @@ -2,9 +2,11 @@ import domain.CoinCountGenerator; import domain.PossesionAmount; +import domain.Products; import domain.VendingMachine; import dto.VendingMachineStatusDto; import service.PossessionAmountService; +import service.ProductsService; import service.VendingMachineService; import view.InputView; import view.OutputView; @@ -21,6 +23,7 @@ public class VendingMachineController { private final VendingMachineService vendingMachineService; private final OutputView outputView; + private final ProductsService productsService; public VendingMachineController(){ inputView = new InputView(); @@ -29,6 +32,7 @@ public VendingMachineController(){ CoinCountGenerator coinCountGenerator = new CoinCountGenerator(); vendingMachineService = new VendingMachineService(vendingMachine, coinCountGenerator); outputView = new OutputView(); + productsService = new ProductsService(); } public void start(){ @@ -37,6 +41,7 @@ public void start(){ List statusList = vendingMachineService.generateRandomCoins(possessionAmount.getAmount()); outputView.printVendingMachineStatus(statusList); String productInfo = getProductInfo(); + Products products = createProducts(productInfo); } private String getPossessionAmount(){ @@ -56,4 +61,8 @@ private String getProductInfo(){ return inputView.readConsole(); }); } + + private Products createProducts(String productInfo){ + return productsService.createProducts(productInfo); + } } diff --git a/src/main/java/service/ProductsService.java b/src/main/java/service/ProductsService.java new file mode 100644 index 000000000..876c4b521 --- /dev/null +++ b/src/main/java/service/ProductsService.java @@ -0,0 +1,10 @@ +package service; + +import domain.PossesionAmount; +import domain.Products; + +public class ProductsService { + public Products createProducts(final String productsInfo){ + return new Products(productsInfo); + } +} From c20a1fd7cb9aec986aaa3bf2ccc24ea5b1efea39 Mon Sep 17 00:00:00 2001 From: suhyun Date: Sat, 25 Nov 2023 22:17:41 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=ED=88=AC=EC=9E=85=EA=B8=88=EC=95=A1=EC=9D=84=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 2 +- .../controller/VendingMachineController.java | 26 ++++++--- src/main/java/domain/Payment.java | 22 ++++++++ src/main/java/domain/PossesionAmount.java | 12 ++--- .../java/domain/wrapper/PaymentAmount.java | 47 ++++++++++++++++ ...{Amount.java => VendingMachineAmount.java} | 16 +++--- src/main/java/service/PaymentService.java | 10 ++++ src/main/java/util/message/InputMessage.java | 3 +- .../domain/wrapper/PaymentAmountTest.java | 53 +++++++++++++++++++ ...est.java => VendingMachineAmountTest.java} | 16 +++--- 10 files changed, 175 insertions(+), 32 deletions(-) create mode 100644 src/main/java/domain/Payment.java create mode 100644 src/main/java/domain/wrapper/PaymentAmount.java rename src/main/java/domain/wrapper/{Amount.java => VendingMachineAmount.java} (75%) create mode 100644 src/main/java/service/PaymentService.java create mode 100644 src/test/java/domain/wrapper/PaymentAmountTest.java rename src/test/java/domain/wrapper/{AmountTest.java => VendingMachineAmountTest.java} (82%) diff --git a/docs/README.md b/docs/README.md index 027b871ca..57a3244c8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,7 @@ - 상품 가격은 100원부터 시작하며, 10원으로 나누어떨어져야 한다. -[ ] 사용자가 투입한 금액으로 상품을 구매할 수 있다. +[ ✅ ] 사용자는 금액을 투입할 수 있다. [ ] 잔돈을 돌려줄 때 현재 보유한 최소 개수의 동전으로 잔돈을 돌려준다. diff --git a/src/main/java/controller/VendingMachineController.java b/src/main/java/controller/VendingMachineController.java index dab3b7361..861beec6e 100644 --- a/src/main/java/controller/VendingMachineController.java +++ b/src/main/java/controller/VendingMachineController.java @@ -1,10 +1,8 @@ package controller; -import domain.CoinCountGenerator; -import domain.PossesionAmount; -import domain.Products; -import domain.VendingMachine; +import domain.*; import dto.VendingMachineStatusDto; +import service.PaymentService; import service.PossessionAmountService; import service.ProductsService; import service.VendingMachineService; @@ -13,8 +11,7 @@ import java.util.List; -import static util.message.InputMessage.INPUT_POSSESSION_AMOUNT_MESSAGE; -import static util.message.InputMessage.INPUT_PRODUCT_DETAIL; +import static util.message.InputMessage.*; import static util.message.OutputMessage.VENDING_MACHINE_STATUS; public class VendingMachineController { @@ -24,6 +21,7 @@ public class VendingMachineController { private final OutputView outputView; private final ProductsService productsService; + private final PaymentService paymentService; public VendingMachineController(){ inputView = new InputView(); @@ -33,15 +31,18 @@ public VendingMachineController(){ vendingMachineService = new VendingMachineService(vendingMachine, coinCountGenerator); outputView = new OutputView(); productsService = new ProductsService(); + paymentService = new PaymentService(); } public void start(){ String amount = getPossessionAmount(); PossesionAmount possessionAmount = createPossessionAmount(amount); - List statusList = vendingMachineService.generateRandomCoins(possessionAmount.getAmount()); + List statusList = vendingMachineService.generateRandomCoins(possessionAmount.getPossessionAmount()); outputView.printVendingMachineStatus(statusList); String productInfo = getProductInfo(); Products products = createProducts(productInfo); + String paymentAmount = getPayment(); + Payment payment = createPayment(paymentAmount); } private String getPossessionAmount(){ @@ -65,4 +66,15 @@ private String getProductInfo(){ private Products createProducts(String productInfo){ return productsService.createProducts(productInfo); } + + private String getPayment(){ + return inputView.getUserInput(() -> { + OutputView.printMessage(INPUT_PAYMENT.getValue()); + return inputView.readConsole(); + }); + } + + private Payment createPayment(String paymentAmount){ + return paymentService.createPayment(paymentAmount); + } } diff --git a/src/main/java/domain/Payment.java b/src/main/java/domain/Payment.java new file mode 100644 index 000000000..88276b707 --- /dev/null +++ b/src/main/java/domain/Payment.java @@ -0,0 +1,22 @@ +package domain; + +import domain.wrapper.PaymentAmount; +import domain.wrapper.VendingMachineAmount; + +public class Payment { + private final PaymentAmount payment; + private Payment(final String payment){ + this.payment = PaymentAmount.create(payment); + } + + public static Payment create(final String payment){ + return new Payment(payment); + } + + //쉽게 말해, getter를 통해 얻은 상태값으로 하려고 했던 '행동'을 + // 그 상태값을 가진 객체가 하도록 '행동'의 주체를 옮기는 것이다. + + public int getPayment(){ + return payment.getPaymentAmount(); + } +} diff --git a/src/main/java/domain/PossesionAmount.java b/src/main/java/domain/PossesionAmount.java index 231e789c4..e4e66a0d2 100644 --- a/src/main/java/domain/PossesionAmount.java +++ b/src/main/java/domain/PossesionAmount.java @@ -1,14 +1,12 @@ package domain; -import domain.wrapper.Amount; - -import java.util.HashMap; +import domain.wrapper.VendingMachineAmount; public class PossesionAmount { - private final Amount amount; + private final VendingMachineAmount vendingMachineAmount; private PossesionAmount(final String possessionAmount){ - this.amount = Amount.create(possessionAmount); + this.vendingMachineAmount = VendingMachineAmount.create(possessionAmount); } public static PossesionAmount create(final String possessionAmount){ @@ -18,7 +16,7 @@ public static PossesionAmount create(final String possessionAmount){ //쉽게 말해, getter를 통해 얻은 상태값으로 하려고 했던 '행동'을 // 그 상태값을 가진 객체가 하도록 '행동'의 주체를 옮기는 것이다. - public int getAmount(){ - return amount.getAmount(); + public int getPossessionAmount(){ + return vendingMachineAmount.getVendingMachineAmount(); } } diff --git a/src/main/java/domain/wrapper/PaymentAmount.java b/src/main/java/domain/wrapper/PaymentAmount.java new file mode 100644 index 000000000..12b2b8ea0 --- /dev/null +++ b/src/main/java/domain/wrapper/PaymentAmount.java @@ -0,0 +1,47 @@ +package domain.wrapper; + +import domain.constant.Constant; + +import static util.message.ExceptionMessage.*; +import static util.message.ExceptionMessage.RANGE_MESSAGE; + +public class PaymentAmount { + private final int paymentAmount; + + private PaymentAmount(final String payment){ + validateNameBlank(payment); + int amount = validateType(payment); + this.paymentAmount = validateRange(amount); + } + + public static PaymentAmount create(final String payment){ + return new PaymentAmount(payment); + } + + public int getPaymentAmount(){ + return paymentAmount; + } + + private void validateNameBlank(final String payment) { + if (payment == null || payment.trim().isEmpty()) { + throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "투입금액")); + } + } + + private int validateType(final String amount) { + int count; + try { + count = Integer.parseInt(amount); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format(TYPE_MESSAGE.getValue(), "투입금액")); + } + return count; + } + + private int validateRange(final int amount) { + if (amount <= Constant.ZERO.getValue()) { + throw new IllegalArgumentException(String.format(RANGE_MESSAGE.getValue(), Constant.ZERO.getValue())); + } + return amount; + } +} diff --git a/src/main/java/domain/wrapper/Amount.java b/src/main/java/domain/wrapper/VendingMachineAmount.java similarity index 75% rename from src/main/java/domain/wrapper/Amount.java rename to src/main/java/domain/wrapper/VendingMachineAmount.java index 3fec20734..6b02558ec 100644 --- a/src/main/java/domain/wrapper/Amount.java +++ b/src/main/java/domain/wrapper/VendingMachineAmount.java @@ -4,21 +4,21 @@ import static util.message.ExceptionMessage.*; -public class Amount { - private final int amount; +public class VendingMachineAmount { + private final int vendingMachineAmount; - private Amount(final String possesionAmount){ + private VendingMachineAmount(final String possesionAmount){ validateNameBlank(possesionAmount); int amount = validateType(possesionAmount); - this.amount = validateDivisibleBy10(validateRange(amount)); + this.vendingMachineAmount = validateDivisibleBy10(validateRange(amount)); } - public static Amount create(final String possesionAmount){ - return new Amount(possesionAmount); + public static VendingMachineAmount create(final String possesionAmount){ + return new VendingMachineAmount(possesionAmount); } - public int getAmount(){ - return amount; + public int getVendingMachineAmount(){ + return vendingMachineAmount; } private void validateNameBlank(final String possesionAmount) { diff --git a/src/main/java/service/PaymentService.java b/src/main/java/service/PaymentService.java new file mode 100644 index 000000000..25844f643 --- /dev/null +++ b/src/main/java/service/PaymentService.java @@ -0,0 +1,10 @@ +package service; + +import domain.Payment; +import domain.PossesionAmount; + +public class PaymentService { + public Payment createPayment(final String payment){ + return Payment.create(payment); + } +} diff --git a/src/main/java/util/message/InputMessage.java b/src/main/java/util/message/InputMessage.java index f6b8565aa..1e5f0e39e 100644 --- a/src/main/java/util/message/InputMessage.java +++ b/src/main/java/util/message/InputMessage.java @@ -5,7 +5,8 @@ public enum InputMessage implements EnumUtil { INPUT_POSSESSION_AMOUNT_MESSAGE("자판기가 보유하고 있는 금액을 입력해 주세요."), - INPUT_PRODUCT_DETAIL("\n상품명과 가격, 수량을 입력해 주세요."); + INPUT_PRODUCT_DETAIL("\n상품명과 가격, 수량을 입력해 주세요."), + INPUT_PAYMENT("\n투입 금액을 입력해 주세요."); private final String message; diff --git a/src/test/java/domain/wrapper/PaymentAmountTest.java b/src/test/java/domain/wrapper/PaymentAmountTest.java new file mode 100644 index 000000000..aa2dbe89e --- /dev/null +++ b/src/test/java/domain/wrapper/PaymentAmountTest.java @@ -0,0 +1,53 @@ +package domain.wrapper; + +import domain.constant.Constant; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static util.message.ExceptionMessage.*; +import static util.message.ExceptionMessage.RANGE_MESSAGE; + +public class PaymentAmountTest { + @ParameterizedTest + @DisplayName("투입금액을 올바르게 입력한 경우 예외가 발생하지 않는다.") + @CsvSource("450") + void givenNormalPaymentAmount_thenSuccess(final String paymentAmount) { + assertThat(PaymentAmount.create(paymentAmount)) + .isInstanceOf(PaymentAmount.class); + + assertThatCode(() -> PaymentAmount.create(paymentAmount)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest + @DisplayName("투입금액을 빈값으로 입력한 경우 예외가 발생한다.") + @ValueSource(strings = {"", " ", " ", " ", " ", "\n", "\t", "\r"}) + void givenBlankPaymentAmount_thenFail(final String paymentAmount) { + assertThatThrownBy(() -> PaymentAmount.create(paymentAmount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(BLANK_MESSAGE.getValue(), "투입금액")); + } + + @ParameterizedTest + @DisplayName("투입금액을 숫자가 아닌 형태로 입력한 경우 예외가 발생한다.") + @ValueSource(strings = {"abc", "12bd"}) + void givenNonNumeric_thenFail(final String paymentAmount) { + assertThatThrownBy(() -> PaymentAmount.create(paymentAmount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(TYPE_MESSAGE.getValue(), "투입금액")); + } + + @ParameterizedTest + @DisplayName("투입금액이 0이하인경우 예외가 발생한다.") + @ValueSource(strings = {"-1", "0"}) + void givenLessZero_thenFail(final String paymentAmount) { + assertThatThrownBy(() -> PaymentAmount.create(paymentAmount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(RANGE_MESSAGE.getValue(), Constant.ZERO.getValue())); + } +} diff --git a/src/test/java/domain/wrapper/AmountTest.java b/src/test/java/domain/wrapper/VendingMachineAmountTest.java similarity index 82% rename from src/test/java/domain/wrapper/AmountTest.java rename to src/test/java/domain/wrapper/VendingMachineAmountTest.java index 3e086804e..064bd9b8c 100644 --- a/src/test/java/domain/wrapper/AmountTest.java +++ b/src/test/java/domain/wrapper/VendingMachineAmountTest.java @@ -11,15 +11,15 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static util.message.ExceptionMessage.*; -public class AmountTest { +public class VendingMachineAmountTest { @ParameterizedTest @DisplayName("보유금액을 올바르게 입력한 경우 예외가 발생하지 않는다.") @CsvSource("450") void givenNormalAmount_thenSuccess(final String amount) { - assertThat(Amount.create(amount)) - .isInstanceOf(Amount.class); + assertThat(VendingMachineAmount.create(amount)) + .isInstanceOf(VendingMachineAmount.class); - assertThatCode(() -> Amount.create(amount)) + assertThatCode(() -> VendingMachineAmount.create(amount)) .doesNotThrowAnyException(); } @@ -27,7 +27,7 @@ void givenNormalAmount_thenSuccess(final String amount) { @DisplayName("보유금액을 빈값으로 입력한 경우 예외가 발생한다.") @ValueSource(strings = {"", " ", " ", " ", " ", "\n", "\t", "\r"}) void givenBlankAmount_thenFail(final String amount) { - assertThatThrownBy(() -> Amount.create(amount)) + assertThatThrownBy(() -> VendingMachineAmount.create(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(String.format(BLANK_MESSAGE.getValue(), "보유금액")); } @@ -36,7 +36,7 @@ void givenBlankAmount_thenFail(final String amount) { @DisplayName("보유금액을 숫자가 아닌 형태로 입력한 경우 예외가 발생한다.") @ValueSource(strings = {"abc", "12bd"}) void givenNonNumeric_thenFail(final String amount) { - assertThatThrownBy(() -> Amount.create(amount)) + assertThatThrownBy(() -> VendingMachineAmount.create(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(String.format(TYPE_MESSAGE.getValue(), "보유금액")); } @@ -45,7 +45,7 @@ void givenNonNumeric_thenFail(final String amount) { @DisplayName("보유금액이 10으로 나누어 떨어지지 않는 경우 예외가 발생한다.") @ValueSource(strings = {"456", "123"}) void givenNonDivisibleBy10_thenFail(final String amount) { - assertThatThrownBy(() -> Amount.create(amount)) + assertThatThrownBy(() -> VendingMachineAmount.create(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(String.format(TEN_UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); } @@ -54,7 +54,7 @@ void givenNonDivisibleBy10_thenFail(final String amount) { @DisplayName("보유금액이 0이하인경우 예외가 발생한다.") @ValueSource(strings = {"-1", "0"}) void givenLessZero_thenFail(final String amount) { - assertThatThrownBy(() -> Amount.create(amount)) + assertThatThrownBy(() -> VendingMachineAmount.create(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(String.format(RANGE_MESSAGE.getValue(), Constant.ZERO.getValue())); } From d854b4d0a1283a6150822846be22df2427a87272 Mon Sep 17 00:00:00 2001 From: suhyun Date: Sun, 26 Nov 2023 16:05:49 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=9E=85=EB=A0=A5=ED=95=9C=20=ED=88=AC=EC=9E=85?= =?UTF-8?q?=EA=B8=88=EC=95=A1=EC=9D=84=20=EC=B6=9C=EB=A0=A5=20=EB=B0=8F=20?= =?UTF-8?q?=EC=83=81=ED=92=88=EB=AA=85=20=EC=9E=85=EB=A0=A5=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 9 ++- .../controller/VendingMachineController.java | 34 ++++++++--- src/main/java/domain/CoinCountGenerator.java | 8 ++- ...esionAmount.java => PossessionAmount.java} | 8 +-- src/main/java/domain/Products.java | 4 ++ src/main/java/domain/SelectedProduct.java | 19 +++++++ src/main/java/domain/VendingMachine.java | 3 + .../domain/wrapper/SelectedProductName.java | 52 +++++++++++++++++ .../domain/wrapper/VendingMachineAmount.java | 2 +- src/main/java/dto/PaymentStatusDto.java | 17 ++++++ src/main/java/service/PaymentService.java | 6 +- .../java/service/PossessionAmountService.java | 6 +- src/main/java/service/ProductsService.java | 1 - .../java/service/SelectedProductService.java | 11 ++++ .../java/service/VendingMachineService.java | 2 +- .../util/exception/NoResourceException.java | 7 +++ src/main/java/util/message/InputMessage.java | 3 +- src/main/java/util/message/OutputMessage.java | 3 +- src/main/java/view/OutputView.java | 5 ++ .../wrapper/SelectedProductNameTest.java | 56 +++++++++++++++++++ .../wrapper/VendingMachineAmountTest.java | 2 +- 21 files changed, 232 insertions(+), 26 deletions(-) rename src/main/java/domain/{PossesionAmount.java => PossessionAmount.java} (69%) create mode 100644 src/main/java/domain/SelectedProduct.java create mode 100644 src/main/java/domain/wrapper/SelectedProductName.java create mode 100644 src/main/java/dto/PaymentStatusDto.java create mode 100644 src/main/java/service/SelectedProductService.java create mode 100644 src/main/java/util/exception/NoResourceException.java create mode 100644 src/test/java/domain/wrapper/SelectedProductNameTest.java diff --git a/docs/README.md b/docs/README.md index 57a3244c8..7f56f04f5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,10 +12,17 @@ [ ✅ ] 사용자는 금액을 투입할 수 있다. +[ ✅ ] 사용자가 입력한 투입금액을 기반으로 현재 투입금액을 출력한다. + +[ ✅ ] 사용자는 구매할 상품명을 입력한다. + [ ] 잔돈을 돌려줄 때 현재 보유한 최소 개수의 동전으로 잔돈을 돌려준다. [ ] 남은 금액이 상품의 최저 가격보다 적거나, 모든 상품이 소진된 경우 바로 잔돈을 돌려준다. [ ] 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환한다. -- 반환되지 않은 금액은 자판기에 남는다. \ No newline at end of file +- 반환되지 않은 금액은 자판기에 남는다. + +### 궁금증 +- VendingMachine의 속성과 PossessionAmount의 getter지양 해결 못함. \ No newline at end of file diff --git a/src/main/java/controller/VendingMachineController.java b/src/main/java/controller/VendingMachineController.java index 861beec6e..66063eef9 100644 --- a/src/main/java/controller/VendingMachineController.java +++ b/src/main/java/controller/VendingMachineController.java @@ -1,18 +1,15 @@ package controller; import domain.*; +import dto.PaymentStatusDto; import dto.VendingMachineStatusDto; -import service.PaymentService; -import service.PossessionAmountService; -import service.ProductsService; -import service.VendingMachineService; +import service.*; import view.InputView; import view.OutputView; import java.util.List; import static util.message.InputMessage.*; -import static util.message.OutputMessage.VENDING_MACHINE_STATUS; public class VendingMachineController { private final InputView inputView; @@ -22,27 +19,46 @@ public class VendingMachineController { private final OutputView outputView; private final ProductsService productsService; private final PaymentService paymentService; + private final VendingMachine vendingMachine; + private final CoinCountGenerator coinCountGenerator; + private final SelectedProductService selectedProductService; public VendingMachineController(){ inputView = new InputView(); possesionAmountService = new PossessionAmountService(); - VendingMachine vendingMachine = new VendingMachine(); - CoinCountGenerator coinCountGenerator = new CoinCountGenerator(); + vendingMachine = new VendingMachine(); + coinCountGenerator = new CoinCountGenerator(); vendingMachineService = new VendingMachineService(vendingMachine, coinCountGenerator); outputView = new OutputView(); productsService = new ProductsService(); paymentService = new PaymentService(); + selectedProductService = new SelectedProductService(); } public void start(){ String amount = getPossessionAmount(); - PossesionAmount possessionAmount = createPossessionAmount(amount); + PossessionAmount possessionAmount = createPossessionAmount(amount); List statusList = vendingMachineService.generateRandomCoins(possessionAmount.getPossessionAmount()); outputView.printVendingMachineStatus(statusList); String productInfo = getProductInfo(); Products products = createProducts(productInfo); String paymentAmount = getPayment(); Payment payment = createPayment(paymentAmount); + PaymentStatusDto paymentStatusDto = paymentService.createPaymentStatusDto(payment); + outputView.printPaymentStatus(paymentStatusDto); + String wantedProduct = getSelectedProduct(); + SelectedProduct selectedProduct = createSelectedProduct(wantedProduct, products); + } + + private SelectedProduct createSelectedProduct(String wantedProduct, final Products products){ + return selectedProductService.createSelectedProduct(wantedProduct, products); + } + + private String getSelectedProduct(){ + return inputView.getUserInput(() -> { + OutputView.printMessage(INPUT_SELECTED_PRODUCT.getValue()); + return inputView.readConsole(); + }); } private String getPossessionAmount(){ @@ -52,7 +68,7 @@ private String getPossessionAmount(){ }); } - private PossesionAmount createPossessionAmount(String possessionAmount){ + private PossessionAmount createPossessionAmount(String possessionAmount){ return possesionAmountService.createPossessionAmount(possessionAmount); } diff --git a/src/main/java/domain/CoinCountGenerator.java b/src/main/java/domain/CoinCountGenerator.java index 3dc7010be..25a506316 100644 --- a/src/main/java/domain/CoinCountGenerator.java +++ b/src/main/java/domain/CoinCountGenerator.java @@ -8,8 +8,12 @@ public class CoinCountGenerator implements CoinGenerator { public int generateRandomCoins(Coin coin, int amount) { int coinCount = 0; while (amount >= coin.getAmount()) { - amount -= coin.getAmount(); - coinCount++; + Coin randomCoin = Coin.pickRandomWithLimit(amount); + if (randomCoin == coin) { + amount -= coin.getAmount(); + coinCount++; + } + // 무작위로 선택된 동전이 현재 생성하려는 동전과 같지 않으면 스킵하고 다시 선택 } return coinCount; } diff --git a/src/main/java/domain/PossesionAmount.java b/src/main/java/domain/PossessionAmount.java similarity index 69% rename from src/main/java/domain/PossesionAmount.java rename to src/main/java/domain/PossessionAmount.java index e4e66a0d2..11eb24348 100644 --- a/src/main/java/domain/PossesionAmount.java +++ b/src/main/java/domain/PossessionAmount.java @@ -2,15 +2,15 @@ import domain.wrapper.VendingMachineAmount; -public class PossesionAmount { +public class PossessionAmount { private final VendingMachineAmount vendingMachineAmount; - private PossesionAmount(final String possessionAmount){ + private PossessionAmount(final String possessionAmount){ this.vendingMachineAmount = VendingMachineAmount.create(possessionAmount); } - public static PossesionAmount create(final String possessionAmount){ - return new PossesionAmount(possessionAmount); + public static PossessionAmount create(final String possessionAmount){ + return new PossessionAmount(possessionAmount); } //쉽게 말해, getter를 통해 얻은 상태값으로 하려고 했던 '행동'을 diff --git a/src/main/java/domain/Products.java b/src/main/java/domain/Products.java index 7c5af143b..f9fc0af70 100644 --- a/src/main/java/domain/Products.java +++ b/src/main/java/domain/Products.java @@ -70,4 +70,8 @@ public int hashCode() { return Objects.hash(products); } + public boolean containsProductName(String productName) { + return products.stream().anyMatch(product -> product.getName().equals(productName)); + } + } diff --git a/src/main/java/domain/SelectedProduct.java b/src/main/java/domain/SelectedProduct.java new file mode 100644 index 000000000..b37da0148 --- /dev/null +++ b/src/main/java/domain/SelectedProduct.java @@ -0,0 +1,19 @@ +package domain; + +import domain.wrapper.PaymentAmount; +import domain.wrapper.SelectedProductName; +import util.exception.NoResourceException; + +import static util.message.ExceptionMessage.BLANK_MESSAGE; +import static util.message.ExceptionMessage.NO_RESOURCE_MESSAGE; + +public class SelectedProduct { + private final SelectedProductName selectedProduct; + private SelectedProduct(final String selectedProduct, final Products products){ + this.selectedProduct = SelectedProductName.create(selectedProduct, products); + } + + public static SelectedProduct create(final String selectedProduct, final Products products){ + return new SelectedProduct(selectedProduct, products); + } +} diff --git a/src/main/java/domain/VendingMachine.java b/src/main/java/domain/VendingMachine.java index 10f3c281c..081e155aa 100644 --- a/src/main/java/domain/VendingMachine.java +++ b/src/main/java/domain/VendingMachine.java @@ -7,6 +7,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +//원래라고 하면 자판기에 보유금액, Products까지 속성으로 되어야할것같은데.. << 이러려면 set이 필요하거나 +//생성자 주입으로 되지 못해.. 애초에 보유금액 입력 후 동전내역이 출력된후 상품 정보를입력받는 순서이기 때문에 +//다른 사람들은 어떻게 한거지..? public class VendingMachine { private final HashMap vendingMachine; diff --git a/src/main/java/domain/wrapper/SelectedProductName.java b/src/main/java/domain/wrapper/SelectedProductName.java new file mode 100644 index 000000000..f6de1cf90 --- /dev/null +++ b/src/main/java/domain/wrapper/SelectedProductName.java @@ -0,0 +1,52 @@ +package domain.wrapper; + +import domain.Products; +import util.exception.NoResourceException; + +import java.util.Objects; + +import static util.message.ExceptionMessage.BLANK_MESSAGE; +import static util.message.ExceptionMessage.NO_RESOURCE_MESSAGE; + +public class SelectedProductName { + private final String name; + + private SelectedProductName(final String name, final Products products){ + validateBlank(name); + this.name = name; + validateExistence(products); + } + + public static SelectedProductName create(final String name, final Products products){ + return new SelectedProductName(name, products); + } + + private void validateBlank(final String name){ + if (name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "구매할 상품명")); + } + } + private void validateExistence(Products products){ + if (!products.containsProductName(name)) { + throw new NoResourceException(String.format(NO_RESOURCE_MESSAGE.getValue(), "해당 상품")); + } + } + + @Override + public boolean equals(Object diffName) { + if (this == diffName) return true; + if (diffName == null || getClass() != diffName.getClass()) return false; + SelectedProductName nameInfo = (SelectedProductName) diffName; + return Objects.equals(name, nameInfo.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + public String getName() { + return name; + } + +} diff --git a/src/main/java/domain/wrapper/VendingMachineAmount.java b/src/main/java/domain/wrapper/VendingMachineAmount.java index 6b02558ec..e18bb9fe3 100644 --- a/src/main/java/domain/wrapper/VendingMachineAmount.java +++ b/src/main/java/domain/wrapper/VendingMachineAmount.java @@ -45,7 +45,7 @@ private int validateDivisibleBy10(final int amount){ } private int validateRange(final int amount) { - if (amount <= Constant.ZERO.getValue()) { + if (amount < Constant.ZERO.getValue()) { throw new IllegalArgumentException(String.format(RANGE_MESSAGE.getValue(), Constant.ZERO.getValue())); } return amount; diff --git a/src/main/java/dto/PaymentStatusDto.java b/src/main/java/dto/PaymentStatusDto.java new file mode 100644 index 000000000..ca8dab1ae --- /dev/null +++ b/src/main/java/dto/PaymentStatusDto.java @@ -0,0 +1,17 @@ +package dto; + +public class PaymentStatusDto { + private final int payment; + + public PaymentStatusDto(final int payment){ + this.payment = payment; + } + + public static PaymentStatusDto create(final int payment) { + return new PaymentStatusDto(payment); + } + + public int getPayment(){ + return payment; + } +} diff --git a/src/main/java/service/PaymentService.java b/src/main/java/service/PaymentService.java index 25844f643..6c4403315 100644 --- a/src/main/java/service/PaymentService.java +++ b/src/main/java/service/PaymentService.java @@ -1,10 +1,14 @@ package service; import domain.Payment; -import domain.PossesionAmount; +import dto.PaymentStatusDto; public class PaymentService { public Payment createPayment(final String payment){ return Payment.create(payment); } + + public PaymentStatusDto createPaymentStatusDto(final Payment payment) { + return PaymentStatusDto.create(payment.getPayment()); + } } diff --git a/src/main/java/service/PossessionAmountService.java b/src/main/java/service/PossessionAmountService.java index 29e870c2a..83fa25155 100644 --- a/src/main/java/service/PossessionAmountService.java +++ b/src/main/java/service/PossessionAmountService.java @@ -1,9 +1,9 @@ package service; -import domain.PossesionAmount; +import domain.PossessionAmount; public class PossessionAmountService { - public PossesionAmount createPossessionAmount(final String possessionAmount){ - return PossesionAmount.create(possessionAmount); + public PossessionAmount createPossessionAmount(final String possessionAmount){ + return PossessionAmount.create(possessionAmount); } } diff --git a/src/main/java/service/ProductsService.java b/src/main/java/service/ProductsService.java index 876c4b521..435089618 100644 --- a/src/main/java/service/ProductsService.java +++ b/src/main/java/service/ProductsService.java @@ -1,6 +1,5 @@ package service; -import domain.PossesionAmount; import domain.Products; public class ProductsService { diff --git a/src/main/java/service/SelectedProductService.java b/src/main/java/service/SelectedProductService.java new file mode 100644 index 000000000..22ffeec0e --- /dev/null +++ b/src/main/java/service/SelectedProductService.java @@ -0,0 +1,11 @@ +package service; + +import domain.PossessionAmount; +import domain.Products; +import domain.SelectedProduct; + +public class SelectedProductService { + public SelectedProduct createSelectedProduct(final String wantedProduct, final Products products){ + return SelectedProduct.create(wantedProduct, products); + } +} diff --git a/src/main/java/service/VendingMachineService.java b/src/main/java/service/VendingMachineService.java index 1dddd1797..52fd60347 100644 --- a/src/main/java/service/VendingMachineService.java +++ b/src/main/java/service/VendingMachineService.java @@ -23,7 +23,7 @@ public List generateRandomCoins(int amount) { for (Coin coin : Coin.values()) { CoinCountGenerator coinCountGenerator = new CoinCountGenerator(); - int coinCount = coinCountGenerator.generateRandomCoins(coin, amount); + int coinCount = coinCountGenerator.generateRandomCoins(coin,amount); vendingMachine.addCoins(coin, coinCount); statusList.add(VendingMachineStatusDto.create(coin.getAmount(), coinCount)); amount -= coin.getAmount() * coinCount; diff --git a/src/main/java/util/exception/NoResourceException.java b/src/main/java/util/exception/NoResourceException.java new file mode 100644 index 000000000..2f9ec9df2 --- /dev/null +++ b/src/main/java/util/exception/NoResourceException.java @@ -0,0 +1,7 @@ +package util.exception; + +public class NoResourceException extends GlobalException{ + public NoResourceException(String message){ + super(message); + } +} diff --git a/src/main/java/util/message/InputMessage.java b/src/main/java/util/message/InputMessage.java index 1e5f0e39e..c5f505554 100644 --- a/src/main/java/util/message/InputMessage.java +++ b/src/main/java/util/message/InputMessage.java @@ -6,7 +6,8 @@ public enum InputMessage implements EnumUtil { INPUT_POSSESSION_AMOUNT_MESSAGE("자판기가 보유하고 있는 금액을 입력해 주세요."), INPUT_PRODUCT_DETAIL("\n상품명과 가격, 수량을 입력해 주세요."), - INPUT_PAYMENT("\n투입 금액을 입력해 주세요."); + INPUT_PAYMENT("\n투입 금액을 입력해 주세요."), + INPUT_SELECTED_PRODUCT("구매할 상품명을 입력해 주세요.\n"); private final String message; diff --git a/src/main/java/util/message/OutputMessage.java b/src/main/java/util/message/OutputMessage.java index 43f58ab74..ca27a1237 100644 --- a/src/main/java/util/message/OutputMessage.java +++ b/src/main/java/util/message/OutputMessage.java @@ -7,7 +7,8 @@ public enum OutputMessage implements EnumUtil { WON("원"), COUNT("개"), HYPHEN(" - "), - VENDING_MACHINE_STATUS("자판기가 보유한 동전\n"); + VENDING_MACHINE_STATUS("\n자판기가 보유한 동전\n"), + PAYMENT_AMOUNT("\n투입 금액 : "); private final String message; diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 3a526673c..03fe528f1 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,5 +1,6 @@ package view; +import dto.PaymentStatusDto; import dto.VendingMachineStatusDto; import util.message.OutputMessage; @@ -18,4 +19,8 @@ public void printVendingMachineStatus(List statusList) System.out.println(String.format("%d" + WON.getValue() + HYPHEN.getValue() + "%d" + COUNT.getValue(), statusDto.getCoin(), statusDto.getCount())); } } + + public void printPaymentStatus(PaymentStatusDto paymentStatusDto){ + System.out.println(PAYMENT_AMOUNT.getValue() + paymentStatusDto.getPayment() + WON.getValue()); + } } diff --git a/src/test/java/domain/wrapper/SelectedProductNameTest.java b/src/test/java/domain/wrapper/SelectedProductNameTest.java new file mode 100644 index 000000000..874a9c3cc --- /dev/null +++ b/src/test/java/domain/wrapper/SelectedProductNameTest.java @@ -0,0 +1,56 @@ +package domain.wrapper; + +import domain.Products; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import provider.TestProvider; +import util.exception.NoResourceException; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static util.message.ExceptionMessage.BLANK_MESSAGE; +import static util.message.ExceptionMessage.NO_RESOURCE_MESSAGE; + +public class SelectedProductNameTest { + + private Products testProducts; + + @BeforeEach + void init() { + String testProductInfos = "[콜라,1500,20];[사이다,1000,10]"; + testProducts = TestProvider.createTestProducts(testProductInfos); + } + + @ParameterizedTest + @DisplayName("정상적으로 입력했을 경우 예외를 처리하지 않는다.") + @ValueSource(strings = {"콜라", "사이다"}) + void givenNormalName_thenSuccess(final String name) { + assertThat(SelectedProductName.create(name, testProducts)) + .isInstanceOf(SelectedProductName.class); + + assertThatCode(() -> SelectedProductName.create(name, testProducts)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest + @DisplayName("구매할 상품명이 공백일 경우 예외를 처리한다.") + @EmptySource + void givenBlankName_thenFail(final String name) { + assertThatThrownBy(() -> SelectedProductName.create(name, testProducts)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(BLANK_MESSAGE.getValue(), "구매할 상품명")); + } + + @ParameterizedTest + @DisplayName("구매할 상품명이 자판기에 없는 경우 예외가 발생한다.") + @ValueSource(strings = {"자두", "케익"}) + void givenBlankPaymentAmount_thenFail(final String productName) { + assertThatThrownBy(() -> SelectedProductName.create(productName, testProducts)) + .isInstanceOf(NoResourceException.class) + .hasMessageContaining(String.format(NO_RESOURCE_MESSAGE.getValue(), "해당 상품")); + } +} diff --git a/src/test/java/domain/wrapper/VendingMachineAmountTest.java b/src/test/java/domain/wrapper/VendingMachineAmountTest.java index 064bd9b8c..ccccf7715 100644 --- a/src/test/java/domain/wrapper/VendingMachineAmountTest.java +++ b/src/test/java/domain/wrapper/VendingMachineAmountTest.java @@ -52,7 +52,7 @@ void givenNonDivisibleBy10_thenFail(final String amount) { @ParameterizedTest @DisplayName("보유금액이 0이하인경우 예외가 발생한다.") - @ValueSource(strings = {"-1", "0"}) + @ValueSource(strings = {"-1", "-3"}) void givenLessZero_thenFail(final String amount) { assertThatThrownBy(() -> VendingMachineAmount.create(amount)) .isInstanceOf(IllegalArgumentException.class) From 8040d5c269f7725e36a266b855b4bc8dff93b355 Mon Sep 17 00:00:00 2001 From: suhyun Date: Sun, 26 Nov 2023 17:16:24 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=9E=85=EB=A0=A5=ED=95=9C=20=ED=88=AC=EC=9E=85?= =?UTF-8?q?=EA=B8=88=EC=95=A1=EC=9D=84=201000=EC=9B=90=EB=8B=A8=EC=9C=84?= =?UTF-8?q?=EB=A1=9C=20=EC=9E=85=EB=A0=A5=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/constant/Constant.java | 3 ++- src/main/java/domain/wrapper/PaymentAmount.java | 9 ++++++++- src/main/java/domain/wrapper/Price.java | 2 +- .../java/domain/wrapper/VendingMachineAmount.java | 2 +- src/main/java/util/message/ExceptionMessage.java | 2 +- src/test/java/domain/wrapper/PaymentAmountTest.java | 11 ++++++++++- src/test/java/domain/wrapper/ProductPriceTest.java | 2 +- .../java/domain/wrapper/VendingMachineAmountTest.java | 2 +- 8 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/main/java/domain/constant/Constant.java b/src/main/java/domain/constant/Constant.java index 11622e082..fa3d5ad67 100644 --- a/src/main/java/domain/constant/Constant.java +++ b/src/main/java/domain/constant/Constant.java @@ -5,7 +5,8 @@ public enum Constant implements EnumUtil { COIN_TEN(10), COIN_HUNDRED(100), - ZERO(0); + ZERO(0), + ONE_THOUSANE(1000); private final int number; diff --git a/src/main/java/domain/wrapper/PaymentAmount.java b/src/main/java/domain/wrapper/PaymentAmount.java index 12b2b8ea0..5d91074c4 100644 --- a/src/main/java/domain/wrapper/PaymentAmount.java +++ b/src/main/java/domain/wrapper/PaymentAmount.java @@ -11,7 +11,7 @@ public class PaymentAmount { private PaymentAmount(final String payment){ validateNameBlank(payment); int amount = validateType(payment); - this.paymentAmount = validateRange(amount); + this.paymentAmount = validateDivisibleBy1000(validateRange(amount)); } public static PaymentAmount create(final String payment){ @@ -44,4 +44,11 @@ private int validateRange(final int amount) { } return amount; } + + private int validateDivisibleBy1000(final int amount){ + if(amount % Constant.ONE_THOUSANE.getValue() != Constant.ZERO.getValue()){ + throw new IllegalArgumentException(String.format(UNIT_MESSAGE.getValue(), Constant.ONE_THOUSANE.getValue())); + } + return amount; + } } diff --git a/src/main/java/domain/wrapper/Price.java b/src/main/java/domain/wrapper/Price.java index e87c532cb..8e2dfc0c7 100644 --- a/src/main/java/domain/wrapper/Price.java +++ b/src/main/java/domain/wrapper/Price.java @@ -46,7 +46,7 @@ private int validateRange(final int amount) { private int validateDivisibleBy10(final int amount){ if(amount % Constant.COIN_TEN.getValue() != Constant.ZERO.getValue()){ - throw new IllegalArgumentException(String.format(TEN_UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); + throw new IllegalArgumentException(String.format(UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); } return amount; } diff --git a/src/main/java/domain/wrapper/VendingMachineAmount.java b/src/main/java/domain/wrapper/VendingMachineAmount.java index e18bb9fe3..fa6c3de5f 100644 --- a/src/main/java/domain/wrapper/VendingMachineAmount.java +++ b/src/main/java/domain/wrapper/VendingMachineAmount.java @@ -39,7 +39,7 @@ private int validateType(final String amount) { private int validateDivisibleBy10(final int amount){ if(amount % Constant.COIN_TEN.getValue() != Constant.ZERO.getValue()){ - throw new IllegalArgumentException(String.format(TEN_UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); + throw new IllegalArgumentException(String.format(UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); } return amount; } diff --git a/src/main/java/util/message/ExceptionMessage.java b/src/main/java/util/message/ExceptionMessage.java index d56573e18..28d576881 100644 --- a/src/main/java/util/message/ExceptionMessage.java +++ b/src/main/java/util/message/ExceptionMessage.java @@ -8,7 +8,7 @@ public enum ExceptionMessage implements EnumUtil { , INPUT_MESSAGE("입력 중에 예기치 못한 오류가 발생하였습니다. 예외 메시지: %s") , TYPE_MESSAGE("%s은(는) 숫자만 입력할 수 있습니다.") , RANGE_MESSAGE("%d 보다 큰 값을 입력해 주세요.") - , TEN_UNIT_MESSAGE("%d원 단위로 입력해 주세요.") + , UNIT_MESSAGE("%d원 단위로 입력해 주세요.") , DUPLICATE_MESSAGE("%s을(를) 중복으로 입력할 수 없습니다.") , NO_RESOURCE_MESSAGE("%s(이)가 존재하지 않습니다.") , NOT_COIN_MESSAGE("해당하는 %d원 동전이 존재하지 않습니다."); diff --git a/src/test/java/domain/wrapper/PaymentAmountTest.java b/src/test/java/domain/wrapper/PaymentAmountTest.java index aa2dbe89e..57dba8354 100644 --- a/src/test/java/domain/wrapper/PaymentAmountTest.java +++ b/src/test/java/domain/wrapper/PaymentAmountTest.java @@ -15,7 +15,7 @@ public class PaymentAmountTest { @ParameterizedTest @DisplayName("투입금액을 올바르게 입력한 경우 예외가 발생하지 않는다.") - @CsvSource("450") + @CsvSource("4000") void givenNormalPaymentAmount_thenSuccess(final String paymentAmount) { assertThat(PaymentAmount.create(paymentAmount)) .isInstanceOf(PaymentAmount.class); @@ -50,4 +50,13 @@ void givenLessZero_thenFail(final String paymentAmount) { .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(String.format(RANGE_MESSAGE.getValue(), Constant.ZERO.getValue())); } + + @ParameterizedTest + @DisplayName("투입금액이 1000으로 나누어 떨어지지 않는 경우 예외가 발생한다.") + @ValueSource(strings = {"4565", "1223"}) + void givenNonDivisibleBy1000_thenFail(final String amount) { + assertThatThrownBy(() -> PaymentAmount.create(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(UNIT_MESSAGE.getValue(), Constant.ONE_THOUSANE.getValue())); + } } diff --git a/src/test/java/domain/wrapper/ProductPriceTest.java b/src/test/java/domain/wrapper/ProductPriceTest.java index 52adf6b5d..449bb49bc 100644 --- a/src/test/java/domain/wrapper/ProductPriceTest.java +++ b/src/test/java/domain/wrapper/ProductPriceTest.java @@ -57,6 +57,6 @@ void givenLessZero_thenFail(final String amount) { void givenNonDivisibleBy10_thenFail(final String amount) { assertThatThrownBy(() -> Price.create(amount)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(String.format(TEN_UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); + .hasMessageContaining(String.format(UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); } } diff --git a/src/test/java/domain/wrapper/VendingMachineAmountTest.java b/src/test/java/domain/wrapper/VendingMachineAmountTest.java index ccccf7715..48e41a560 100644 --- a/src/test/java/domain/wrapper/VendingMachineAmountTest.java +++ b/src/test/java/domain/wrapper/VendingMachineAmountTest.java @@ -47,7 +47,7 @@ void givenNonNumeric_thenFail(final String amount) { void givenNonDivisibleBy10_thenFail(final String amount) { assertThatThrownBy(() -> VendingMachineAmount.create(amount)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(String.format(TEN_UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); + .hasMessageContaining(String.format(UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); } @ParameterizedTest From 4411fb380d17301959ca70d92b7e2723754ca804 Mon Sep 17 00:00:00 2001 From: suhyun Date: Tue, 28 Nov 2023 03:20:33 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20=EC=9E=94=EB=8F=88=EC=9D=84=20?= =?UTF-8?q?=EB=8F=8C=EB=A0=A4=EC=A4=84=20=EB=95=8C=20=ED=98=84=EC=9E=AC=20?= =?UTF-8?q?=EB=B3=B4=EC=9C=A0=ED=95=9C=20=EC=B5=9C=EC=86=8C=20=EA=B0=9C?= =?UTF-8?q?=EC=88=98=EC=9D=98=20=EB=8F=99=EC=A0=84=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=EB=B0=8F=20=EB=82=A8=EC=9D=80=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=EC=9D=B4=20=EC=83=81=ED=92=88=EC=9D=98=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=80=20=EA=B0=80=EA=B2=A9=EB=B3=B4=EB=8B=A4=20=EC=A0=81?= =?UTF-8?q?=EA=B1=B0=EB=82=98,=20=EB=AA=A8=EB=93=A0=20=EC=83=81=ED=92=88?= =?UTF-8?q?=EC=9D=B4=20=EC=86=8C=EC=A7=84=EB=90=9C=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EB=B0=94=EB=A1=9C=20=EC=9E=94=EB=8F=88=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 11 +-- .../java/controller/ProductsController.java | 74 +++++++++++++++++ .../controller/UserPaymentController.java | 46 +++++++++++ .../controller/VendingMachineController.java | 80 ++++++------------- src/main/java/domain/CoinCountGenerator.java | 20 ----- src/main/java/domain/Payment.java | 16 +++- src/main/java/domain/PossessionAmount.java | 3 - src/main/java/domain/Product.java | 18 +++++ src/main/java/domain/Products.java | 19 ++++- src/main/java/domain/SelectedProduct.java | 19 ----- src/main/java/domain/VendingMachine.java | 16 ++-- .../java/domain/wrapper/PaymentAmount.java | 8 ++ src/main/java/domain/wrapper/Quantity.java | 27 ++++++- .../domain/wrapper/SelectedProductName.java | 52 ------------ .../domain/wrapper/VendingMachineAmount.java | 10 +-- src/main/java/dto/CoinsDto.java | 54 +++++++++++++ src/main/java/dto/ProductNameDto.java | 19 +++++ .../java/dto/VendingMachineStatusDto.java | 8 -- .../java/repository/ProductsRepository.java | 48 +++++++++++ .../repository/UserPaymentRepository.java | 34 ++++++++ .../repository/VendingMachineRepository.java | 32 ++++++++ src/main/java/service/PaymentService.java | 14 ---- src/main/java/service/ProductsService.java | 52 +++++++++++- .../java/service/SelectedProductService.java | 11 --- src/main/java/service/UserPaymentService.java | 22 +++++ .../java/service/VendingMachineService.java | 61 ++++++++++---- src/main/java/util/CoinGenerator.java | 7 -- .../exception/NotEnoughBalanceException.java | 8 ++ .../java/util/exception/SoldOutException.java | 9 +++ src/main/java/util/message/InputMessage.java | 2 +- src/main/java/util/message/OutputMessage.java | 16 ++-- src/main/java/vendingmachine/Application.java | 32 +++++++- src/main/java/view/OutputView.java | 55 ++++++++++--- .../domain/wrapper/ProductQuantityTest.java | 1 - .../wrapper/SelectedProductNameTest.java | 56 ------------- .../service/VendingMachineServiceTest.java | 39 --------- 36 files changed, 653 insertions(+), 346 deletions(-) create mode 100644 src/main/java/controller/ProductsController.java create mode 100644 src/main/java/controller/UserPaymentController.java delete mode 100644 src/main/java/domain/CoinCountGenerator.java delete mode 100644 src/main/java/domain/SelectedProduct.java delete mode 100644 src/main/java/domain/wrapper/SelectedProductName.java create mode 100644 src/main/java/dto/CoinsDto.java create mode 100644 src/main/java/dto/ProductNameDto.java create mode 100644 src/main/java/repository/ProductsRepository.java create mode 100644 src/main/java/repository/UserPaymentRepository.java create mode 100644 src/main/java/repository/VendingMachineRepository.java delete mode 100644 src/main/java/service/PaymentService.java delete mode 100644 src/main/java/service/SelectedProductService.java create mode 100644 src/main/java/service/UserPaymentService.java delete mode 100644 src/main/java/util/CoinGenerator.java create mode 100644 src/main/java/util/exception/NotEnoughBalanceException.java create mode 100644 src/main/java/util/exception/SoldOutException.java delete mode 100644 src/test/java/domain/wrapper/SelectedProductNameTest.java delete mode 100644 src/test/java/service/VendingMachineServiceTest.java diff --git a/docs/README.md b/docs/README.md index 7f56f04f5..c201d1457 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,13 +16,14 @@ [ ✅ ] 사용자는 구매할 상품명을 입력한다. -[ ] 잔돈을 돌려줄 때 현재 보유한 최소 개수의 동전으로 잔돈을 돌려준다. +[ ✅ ] 잔돈을 돌려줄 때 현재 보유한 최소 개수의 동전으로 잔돈을 돌려준다. -[ ] 남은 금액이 상품의 최저 가격보다 적거나, 모든 상품이 소진된 경우 바로 잔돈을 돌려준다. +[ ✅ ] 남은 금액이 상품의 최저 가격보다 적거나, 모든 상품이 소진된 경우 바로 잔돈을 돌려준다. -[ ] 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환한다. +[ ✅ ] 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환한다. - 반환되지 않은 금액은 자판기에 남는다. -### 궁금증 -- VendingMachine의 속성과 PossessionAmount의 getter지양 해결 못함. \ No newline at end of file + +## Getter지양 방법 이후 적용하기 +쉽게 말해, getter를 통해 얻은 상태값으로 하려고 했던 '행동'을 그 상태값을 가진 객체가 하도록 '행동'의 주체를 옮기는 것이다. \ No newline at end of file diff --git a/src/main/java/controller/ProductsController.java b/src/main/java/controller/ProductsController.java new file mode 100644 index 000000000..a328e5b2f --- /dev/null +++ b/src/main/java/controller/ProductsController.java @@ -0,0 +1,74 @@ +package controller; + +import domain.Payment; +import domain.Products; +import dto.ProductNameDto; +import service.ProductsService; +import service.UserPaymentService; +import view.InputView; +import view.OutputView; + +import static util.message.InputMessage.INPUT_PRODUCT_DETAIL; +import static util.message.InputMessage.INPUT_SELECTED_PRODUCT; +import static view.OutputView.printCurrentUserBalance; + +public class ProductsController { + private final ProductsService productsService; + private final UserPaymentService userPaymentService; + private Products products; + + public ProductsController(){ + productsService = new ProductsService(); + userPaymentService = new UserPaymentService(); + } + + public void initProductInfo(InputView inputView, OutputView outputView){ + String productInfo = getProductInfo(inputView); + products = createProducts(productInfo); + } + + private String getProductInfo(InputView inputView){ + return inputView.getUserInput(() -> { + OutputView.printMessage(INPUT_PRODUCT_DETAIL.getValue()); + return inputView.readConsole(); + }); + } + + private Products createProducts(String productInfo){ + return productsService.createProducts(productInfo); + } + + public void buyProduct(InputView inputView){ + String wantedProduct = getSelectedProduct(inputView); + ProductNameDto productNameDto = createSelectedProduct(wantedProduct); + try{ + productsService.buyProduct(productNameDto); + Payment payment = userPaymentService.getUserPayment(); + OutputView.printCurrentUserBalance(payment.getPayment()); + } catch(IllegalArgumentException e){ + OutputView.printError(e.getMessage()); + buyProduct(inputView); + } + } + + private ProductNameDto createSelectedProduct(String wantedProduct){ + return ProductNameDto.create(wantedProduct); + } + + private String getSelectedProduct(InputView inputView){ + return inputView.getUserInput(() -> { + OutputView.printMessage(INPUT_SELECTED_PRODUCT.getValue()); + return inputView.readConsole(); + }); + } + + public boolean checkAvailableToPurchase() { + Payment payment = userPaymentService.getUserPayment(); + + boolean isSoldOutOfItemAvailableForBuy = productsService.checkSoldOutOfItemAvailableForBuy(); + boolean isUserBalanceNotEnough = payment.getPayment() < productsService.getMinItemPrice(); + + return !isSoldOutOfItemAvailableForBuy && !isUserBalanceNotEnough; + } +} + diff --git a/src/main/java/controller/UserPaymentController.java b/src/main/java/controller/UserPaymentController.java new file mode 100644 index 000000000..6e5546788 --- /dev/null +++ b/src/main/java/controller/UserPaymentController.java @@ -0,0 +1,46 @@ +package controller; + +import domain.Payment; +import dto.PaymentStatusDto; +import service.UserPaymentService; +import view.InputView; +import view.OutputView; + +import static util.message.InputMessage.INPUT_PAYMENT; + +public class UserPaymentController { + private final UserPaymentService userPaymentService; + + public UserPaymentController(){ + userPaymentService = new UserPaymentService(); + } + + public void generateUserBalance(InputView inputView, OutputView outputView) { + + try { + initUserPayment(inputView, outputView); + } catch (IllegalArgumentException e) { + OutputView.printError(e.getMessage()); + generateUserBalance(inputView, outputView); + } + } + + public void initUserPayment(InputView inputView, OutputView outputView){ + String paymentAmount = getPayment(inputView); + Payment payment = createPayment(paymentAmount); + PaymentStatusDto paymentStatusDto = userPaymentService.createPaymentStatusDto(payment); + outputView.printPaymentStatus(paymentStatusDto); + } + + private String getPayment(InputView inputView){ + return inputView.getUserInput(() -> { + OutputView.printMessage(INPUT_PAYMENT.getValue()); + return inputView.readConsole(); + }); + } + + private Payment createPayment(String paymentAmount){ + return userPaymentService.createPayment(paymentAmount); + } +} + diff --git a/src/main/java/controller/VendingMachineController.java b/src/main/java/controller/VendingMachineController.java index 66063eef9..df72db72e 100644 --- a/src/main/java/controller/VendingMachineController.java +++ b/src/main/java/controller/VendingMachineController.java @@ -1,7 +1,7 @@ package controller; import domain.*; -import dto.PaymentStatusDto; +import dto.CoinsDto; import dto.VendingMachineStatusDto; import service.*; import view.InputView; @@ -12,56 +12,37 @@ import static util.message.InputMessage.*; public class VendingMachineController { - private final InputView inputView; private final PossessionAmountService possesionAmountService; private final VendingMachineService vendingMachineService; - private final OutputView outputView; - private final ProductsService productsService; - private final PaymentService paymentService; - private final VendingMachine vendingMachine; - private final CoinCountGenerator coinCountGenerator; - private final SelectedProductService selectedProductService; - public VendingMachineController(){ - inputView = new InputView(); possesionAmountService = new PossessionAmountService(); - vendingMachine = new VendingMachine(); - coinCountGenerator = new CoinCountGenerator(); - vendingMachineService = new VendingMachineService(vendingMachine, coinCountGenerator); - outputView = new OutputView(); - productsService = new ProductsService(); - paymentService = new PaymentService(); - selectedProductService = new SelectedProductService(); + vendingMachineService = new VendingMachineService(); } - public void start(){ - String amount = getPossessionAmount(); - PossessionAmount possessionAmount = createPossessionAmount(amount); - List statusList = vendingMachineService.generateRandomCoins(possessionAmount.getPossessionAmount()); - outputView.printVendingMachineStatus(statusList); - String productInfo = getProductInfo(); - Products products = createProducts(productInfo); - String paymentAmount = getPayment(); - Payment payment = createPayment(paymentAmount); - PaymentStatusDto paymentStatusDto = paymentService.createPaymentStatusDto(payment); - outputView.printPaymentStatus(paymentStatusDto); - String wantedProduct = getSelectedProduct(); - SelectedProduct selectedProduct = createSelectedProduct(wantedProduct, products); + public void generateCoins(InputView inputView, OutputView outputView) { + PossessionAmount possessionAmount = initPossessionAmount(inputView, outputView); + + try { + initCoins(possessionAmount); + } catch (IllegalArgumentException e) { + OutputView.printError(e.getMessage()); + generateCoins(inputView, outputView); + } } - private SelectedProduct createSelectedProduct(String wantedProduct, final Products products){ - return selectedProductService.createSelectedProduct(wantedProduct, products); + public void initCoins(PossessionAmount possessionAmount){ + vendingMachineService.generateRandomCoins(possessionAmount.getPossessionAmount()); } - private String getSelectedProduct(){ - return inputView.getUserInput(() -> { - OutputView.printMessage(INPUT_SELECTED_PRODUCT.getValue()); - return inputView.readConsole(); - }); + public PossessionAmount initPossessionAmount(InputView inputView, OutputView outputView){ + String amount = getPossessionAmount(inputView); + return createPossessionAmount(amount); } - private String getPossessionAmount(){ + + + private String getPossessionAmount(InputView inputView){ return inputView.getUserInput(() -> { OutputView.printMessage(INPUT_POSSESSION_AMOUNT_MESSAGE.getValue()); return inputView.readConsole(); @@ -72,25 +53,14 @@ private PossessionAmount createPossessionAmount(String possessionAmount){ return possesionAmountService.createPossessionAmount(possessionAmount); } - private String getProductInfo(){ - return inputView.getUserInput(() -> { - OutputView.printMessage(INPUT_PRODUCT_DETAIL.getValue()); - return inputView.readConsole(); - }); - } - - private Products createProducts(String productInfo){ - return productsService.createProducts(productInfo); + public void printChange() { + CoinsDto coinsDto = vendingMachineService.getChange(); + OutputView.printChange(coinsDto); } - private String getPayment(){ - return inputView.getUserInput(() -> { - OutputView.printMessage(INPUT_PAYMENT.getValue()); - return inputView.readConsole(); - }); + public void printGeneratedCoins() { + CoinsDto coinsDto = vendingMachineService.getCurrentCoins(); + OutputView.printVendingMachineHoldingCoins(coinsDto); } - private Payment createPayment(String paymentAmount){ - return paymentService.createPayment(paymentAmount); - } } diff --git a/src/main/java/domain/CoinCountGenerator.java b/src/main/java/domain/CoinCountGenerator.java deleted file mode 100644 index 25a506316..000000000 --- a/src/main/java/domain/CoinCountGenerator.java +++ /dev/null @@ -1,20 +0,0 @@ -package domain; - -import util.CoinGenerator; - -public class CoinCountGenerator implements CoinGenerator { - - @Override - public int generateRandomCoins(Coin coin, int amount) { - int coinCount = 0; - while (amount >= coin.getAmount()) { - Coin randomCoin = Coin.pickRandomWithLimit(amount); - if (randomCoin == coin) { - amount -= coin.getAmount(); - coinCount++; - } - // 무작위로 선택된 동전이 현재 생성하려는 동전과 같지 않으면 스킵하고 다시 선택 - } - return coinCount; - } -} diff --git a/src/main/java/domain/Payment.java b/src/main/java/domain/Payment.java index 88276b707..323124bae 100644 --- a/src/main/java/domain/Payment.java +++ b/src/main/java/domain/Payment.java @@ -1,6 +1,7 @@ package domain; import domain.wrapper.PaymentAmount; +import domain.wrapper.Price; import domain.wrapper.VendingMachineAmount; public class Payment { @@ -9,14 +10,23 @@ private Payment(final String payment){ this.payment = PaymentAmount.create(payment); } + private Payment(int payment){ + this.payment = PaymentAmount.create(payment); + } + public static Payment create(final String payment){ return new Payment(payment); } - //쉽게 말해, getter를 통해 얻은 상태값으로 하려고 했던 '행동'을 - // 그 상태값을 가진 객체가 하도록 '행동'의 주체를 옮기는 것이다. - public int getPayment(){ return payment.getPaymentAmount(); } + + public boolean canBuy(Product product) { + return payment.getPaymentAmount() >= product.getPrice(); + } + + public Payment subtract(int price) { + return new Payment(payment.getPaymentAmount() - price); + } } diff --git a/src/main/java/domain/PossessionAmount.java b/src/main/java/domain/PossessionAmount.java index 11eb24348..a2302d07e 100644 --- a/src/main/java/domain/PossessionAmount.java +++ b/src/main/java/domain/PossessionAmount.java @@ -13,9 +13,6 @@ public static PossessionAmount create(final String possessionAmount){ return new PossessionAmount(possessionAmount); } - //쉽게 말해, getter를 통해 얻은 상태값으로 하려고 했던 '행동'을 - // 그 상태값을 가진 객체가 하도록 '행동'의 주체를 옮기는 것이다. - public int getPossessionAmount(){ return vendingMachineAmount.getVendingMachineAmount(); } diff --git a/src/main/java/domain/Product.java b/src/main/java/domain/Product.java index dbf0b55d2..e926648d8 100644 --- a/src/main/java/domain/Product.java +++ b/src/main/java/domain/Product.java @@ -13,6 +13,7 @@ public class Product { private final Name name; private final Price price; private final Quantity quantity; + private static final int SOLD_OUT_QUANTITY = 0; private Product(final String productDetail){ validateBlank(productDetail); @@ -26,6 +27,12 @@ public static Product create(final String productDetail){ return new Product(productDetail); } + private Product(Name name, Price price, Quantity quantity){ + this.name = name; + this.price = price; + this.quantity = quantity; + } + private String[] splitProduct(final String productDetail){ return productDetail.split(SPLIT_DELIMITER_COMMA.getValue()); } @@ -62,4 +69,15 @@ public int getPrice() { public int getQuantity() { return quantity.getQuantity(); } + + public boolean isSoldOut() { + return quantity.getQuantity() <= SOLD_OUT_QUANTITY; + } + + public Product decreaseQuantity() { + Quantity subtractedQuantity = quantity.subtract(); + return new Product(name, price, subtractedQuantity); + } + } + diff --git a/src/main/java/domain/Products.java b/src/main/java/domain/Products.java index f9fc0af70..7b7f85ebc 100644 --- a/src/main/java/domain/Products.java +++ b/src/main/java/domain/Products.java @@ -6,8 +6,7 @@ import java.util.stream.Collectors; import static domain.constant.ProductsConstant.*; -import static util.message.ExceptionMessage.BLANK_MESSAGE; -import static util.message.ExceptionMessage.DUPLICATE_MESSAGE; +import static util.message.ExceptionMessage.*; public class Products { private final List products; @@ -19,6 +18,10 @@ public Products(final String productsInfo){ validateDuplicateProductName(); } + public List getProducts() { + return products; + } + private void validateBlank(final String productsInfo){ if (productsInfo == null || productsInfo.trim().isEmpty()) { throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "상품정보")); @@ -70,8 +73,16 @@ public int hashCode() { return Objects.hash(products); } - public boolean containsProductName(String productName) { - return products.stream().anyMatch(product -> product.getName().equals(productName)); + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + sb.append("입력한 상품 정보").append('\n'); + for(Product product : products){ + sb.append(product.getName() + " " + product.getPrice() + " " + product.getQuantity()).append('\n'); + } + return sb.toString(); } + } + diff --git a/src/main/java/domain/SelectedProduct.java b/src/main/java/domain/SelectedProduct.java deleted file mode 100644 index b37da0148..000000000 --- a/src/main/java/domain/SelectedProduct.java +++ /dev/null @@ -1,19 +0,0 @@ -package domain; - -import domain.wrapper.PaymentAmount; -import domain.wrapper.SelectedProductName; -import util.exception.NoResourceException; - -import static util.message.ExceptionMessage.BLANK_MESSAGE; -import static util.message.ExceptionMessage.NO_RESOURCE_MESSAGE; - -public class SelectedProduct { - private final SelectedProductName selectedProduct; - private SelectedProduct(final String selectedProduct, final Products products){ - this.selectedProduct = SelectedProductName.create(selectedProduct, products); - } - - public static SelectedProduct create(final String selectedProduct, final Products products){ - return new SelectedProduct(selectedProduct, products); - } -} diff --git a/src/main/java/domain/VendingMachine.java b/src/main/java/domain/VendingMachine.java index 081e155aa..92b80a2e7 100644 --- a/src/main/java/domain/VendingMachine.java +++ b/src/main/java/domain/VendingMachine.java @@ -1,15 +1,7 @@ package domain; -import camp.nextstep.edu.missionutils.Randoms; -import dto.VendingMachineStatusDto; - -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; -//원래라고 하면 자판기에 보유금액, Products까지 속성으로 되어야할것같은데.. << 이러려면 set이 필요하거나 -//생성자 주입으로 되지 못해.. 애초에 보유금액 입력 후 동전내역이 출력된후 상품 정보를입력받는 순서이기 때문에 -//다른 사람들은 어떻게 한거지..? public class VendingMachine { private final HashMap vendingMachine; @@ -28,4 +20,12 @@ private void init(){ public void addCoins(Coin coin, int count) { vendingMachine.put(coin, vendingMachine.get(coin) + count); } + + public int findByCoin(Coin coin) { + return vendingMachine.get(coin); + } + + public Map findAll() { + return vendingMachine; + } } diff --git a/src/main/java/domain/wrapper/PaymentAmount.java b/src/main/java/domain/wrapper/PaymentAmount.java index 5d91074c4..b10e37cfa 100644 --- a/src/main/java/domain/wrapper/PaymentAmount.java +++ b/src/main/java/domain/wrapper/PaymentAmount.java @@ -14,10 +14,18 @@ private PaymentAmount(final String payment){ this.paymentAmount = validateDivisibleBy1000(validateRange(amount)); } + private PaymentAmount(final int paymentAmount){ + this.paymentAmount = paymentAmount; + } + public static PaymentAmount create(final String payment){ return new PaymentAmount(payment); } + public static PaymentAmount create(final int paymentAmount){ + return new PaymentAmount(paymentAmount); + } + public int getPaymentAmount(){ return paymentAmount; } diff --git a/src/main/java/domain/wrapper/Quantity.java b/src/main/java/domain/wrapper/Quantity.java index 066bf0684..cd11e6ecb 100644 --- a/src/main/java/domain/wrapper/Quantity.java +++ b/src/main/java/domain/wrapper/Quantity.java @@ -1,14 +1,18 @@ package domain.wrapper; import domain.constant.Constant; +import util.exception.NotEnoughBalanceException; +import util.exception.SoldOutException; +import util.message.ExceptionMessage; import java.util.Objects; import static util.message.ExceptionMessage.*; -import static util.message.ExceptionMessage.TEN_UNIT_MESSAGE; public class Quantity { - private final int quantity; + + private static final int SUBTRACT_QUANTITY = 1; + private int quantity; private Quantity(final String quantityInfo){ validateBlank(quantityInfo); @@ -17,10 +21,18 @@ private Quantity(final String quantityInfo){ this.quantity = amount; } + private Quantity(int quantity) { + this.quantity = quantity; + } + public static Quantity create(final String quantityInfo){ return new Quantity(quantityInfo); } + public void add(int amount) { + this.quantity += amount; + } + private void validateBlank(final String productDetail){ if (productDetail == null || productDetail.trim().isEmpty()) { throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "수량")); @@ -60,4 +72,15 @@ public int hashCode() { public int getQuantity() { return quantity; } + + public Quantity subtract() { + validateAbleToSubtractItemQuantity(quantity); + return new Quantity(quantity - SUBTRACT_QUANTITY); + } + + private void validateAbleToSubtractItemQuantity(int quantity){ + if (quantity <= 0) { + throw new SoldOutException(); + } + } } diff --git a/src/main/java/domain/wrapper/SelectedProductName.java b/src/main/java/domain/wrapper/SelectedProductName.java deleted file mode 100644 index f6de1cf90..000000000 --- a/src/main/java/domain/wrapper/SelectedProductName.java +++ /dev/null @@ -1,52 +0,0 @@ -package domain.wrapper; - -import domain.Products; -import util.exception.NoResourceException; - -import java.util.Objects; - -import static util.message.ExceptionMessage.BLANK_MESSAGE; -import static util.message.ExceptionMessage.NO_RESOURCE_MESSAGE; - -public class SelectedProductName { - private final String name; - - private SelectedProductName(final String name, final Products products){ - validateBlank(name); - this.name = name; - validateExistence(products); - } - - public static SelectedProductName create(final String name, final Products products){ - return new SelectedProductName(name, products); - } - - private void validateBlank(final String name){ - if (name == null || name.trim().isEmpty()) { - throw new IllegalArgumentException(String.format(BLANK_MESSAGE.getValue(), "구매할 상품명")); - } - } - private void validateExistence(Products products){ - if (!products.containsProductName(name)) { - throw new NoResourceException(String.format(NO_RESOURCE_MESSAGE.getValue(), "해당 상품")); - } - } - - @Override - public boolean equals(Object diffName) { - if (this == diffName) return true; - if (diffName == null || getClass() != diffName.getClass()) return false; - SelectedProductName nameInfo = (SelectedProductName) diffName; - return Objects.equals(name, nameInfo.name); - } - - @Override - public int hashCode() { - return Objects.hash(name); - } - - public String getName() { - return name; - } - -} diff --git a/src/main/java/domain/wrapper/VendingMachineAmount.java b/src/main/java/domain/wrapper/VendingMachineAmount.java index fa6c3de5f..ebc4b454a 100644 --- a/src/main/java/domain/wrapper/VendingMachineAmount.java +++ b/src/main/java/domain/wrapper/VendingMachineAmount.java @@ -10,7 +10,9 @@ public class VendingMachineAmount { private VendingMachineAmount(final String possesionAmount){ validateNameBlank(possesionAmount); int amount = validateType(possesionAmount); - this.vendingMachineAmount = validateDivisibleBy10(validateRange(amount)); + validateRange(amount); + validateDivisibleBy10(amount); + this.vendingMachineAmount = amount; } public static VendingMachineAmount create(final String possesionAmount){ @@ -37,17 +39,15 @@ private int validateType(final String amount) { return count; } - private int validateDivisibleBy10(final int amount){ + private void validateDivisibleBy10(final int amount){ if(amount % Constant.COIN_TEN.getValue() != Constant.ZERO.getValue()){ throw new IllegalArgumentException(String.format(UNIT_MESSAGE.getValue(), Constant.COIN_TEN.getValue())); } - return amount; } - private int validateRange(final int amount) { + private void validateRange(final int amount) { if (amount < Constant.ZERO.getValue()) { throw new IllegalArgumentException(String.format(RANGE_MESSAGE.getValue(), Constant.ZERO.getValue())); } - return amount; } } diff --git a/src/main/java/dto/CoinsDto.java b/src/main/java/dto/CoinsDto.java new file mode 100644 index 000000000..1eb615aa9 --- /dev/null +++ b/src/main/java/dto/CoinsDto.java @@ -0,0 +1,54 @@ +package dto; + +import domain.Coin; + +import java.util.Map; + +public class CoinsDto { + int coin500Quantity; + int coin100Quantity; + int coin50Quantity; + int coin10Quantity; + + private CoinsDto(int coin500Quantity, int coin100Quantity, int coin50Quantity, int coin10Quantity) { + this.coin500Quantity = coin500Quantity; + this.coin100Quantity = coin100Quantity; + this.coin50Quantity = coin50Quantity; + this.coin10Quantity = coin10Quantity; + } + + public static CoinsDto from(Map coins) { + return new CoinsDto( + coins.get(Coin.COIN_500), + coins.get(Coin.COIN_100), + coins.get(Coin.COIN_50), + coins.get(Coin.COIN_10) + ); + } + + public int getCoin500Quantity() { + return coin500Quantity; + } + + public int getCoin100Quantity() { + return coin100Quantity; + } + + public int getCoin50Quantity() { + return coin50Quantity; + } + + public int getCoin10Quantity() { + return coin10Quantity; + } + + public int getAllCoinQuantity() { + return coin500Quantity + coin100Quantity + coin50Quantity + coin10Quantity; + } + + public String toString(){ + StringBuilder sb = new StringBuilder(); + sb.append(coin500Quantity + "," + coin100Quantity + "," + coin50Quantity + "," + coin10Quantity); + return sb.toString(); + } +} diff --git a/src/main/java/dto/ProductNameDto.java b/src/main/java/dto/ProductNameDto.java new file mode 100644 index 000000000..8b2bc9589 --- /dev/null +++ b/src/main/java/dto/ProductNameDto.java @@ -0,0 +1,19 @@ +package dto; + +import domain.wrapper.Name; + +public class ProductNameDto { + private final String productName; + + private ProductNameDto(String productName){ + this.productName = productName; + } + + public static ProductNameDto create(String productName){ + return new ProductNameDto(productName); + } + + public Name toEntity(){ + return Name.create(productName); + } +} diff --git a/src/main/java/dto/VendingMachineStatusDto.java b/src/main/java/dto/VendingMachineStatusDto.java index 9db254f87..0605b9606 100644 --- a/src/main/java/dto/VendingMachineStatusDto.java +++ b/src/main/java/dto/VendingMachineStatusDto.java @@ -12,12 +12,4 @@ private VendingMachineStatusDto(final int coin, final int count){ public static VendingMachineStatusDto create(final int coin, final int count) { return new VendingMachineStatusDto(coin, count); } - - public int getCount(){ - return count; - } - - public int getCoin(){ - return coin; - } } diff --git a/src/main/java/repository/ProductsRepository.java b/src/main/java/repository/ProductsRepository.java new file mode 100644 index 000000000..e481c7127 --- /dev/null +++ b/src/main/java/repository/ProductsRepository.java @@ -0,0 +1,48 @@ +package repository; + +import domain.Product; +import domain.Products; +import domain.wrapper.Name; +import util.exception.DuplicateException; +import util.exception.NoResourceException; + +import static util.message.ExceptionMessage.DUPLICATE_MESSAGE; +import static util.message.ExceptionMessage.NO_RESOURCE_MESSAGE; + +public class ProductsRepository { + + private static final ProductsRepository instance = new ProductsRepository(); + private Products products; + + private ProductsRepository(){ + + } + + //다음 거에 적용할 부분! + public static ProductsRepository getInstance(){ + return instance; + } + + public Products findAll() { + return products; + } + + public Products createProducts(final String productsInfo){ + this.products = new Products(productsInfo); + return products; + } + public void updateByItemName(Name name, Product product) { + Product originalItem = findByProductName(name); + int itemIndex = products.getProducts().indexOf(originalItem); + products.getProducts().set(itemIndex, product); + } + + public Product findByProductName(Name name) { + return products.getProducts().stream() + .filter(product -> product.getName().equals(name.getName())) + .findFirst() + .orElseThrow(() -> + new NoResourceException(String.format(NO_RESOURCE_MESSAGE.getValue(), "해당 상품"))); + } + +} diff --git a/src/main/java/repository/UserPaymentRepository.java b/src/main/java/repository/UserPaymentRepository.java new file mode 100644 index 000000000..8cf775900 --- /dev/null +++ b/src/main/java/repository/UserPaymentRepository.java @@ -0,0 +1,34 @@ +package repository; + +import domain.Payment; +import dto.PaymentStatusDto; + +public class UserPaymentRepository { + private static final UserPaymentRepository userPaymentRepository = new UserPaymentRepository(); + private Payment userPayment; + + private UserPaymentRepository(){ + + } + + public static UserPaymentRepository getInstance() { + return userPaymentRepository; + } + + public Payment createPayment(final String payment){ + this.userPayment = Payment.create(payment); + return userPayment; + } + + public PaymentStatusDto createPaymentStatusDto(final Payment payment) { + return PaymentStatusDto.create(payment.getPayment()); + } + + public Payment get() { + return userPayment; + } + + public void update(Payment payment) { + this.userPayment = payment; + } +} diff --git a/src/main/java/repository/VendingMachineRepository.java b/src/main/java/repository/VendingMachineRepository.java new file mode 100644 index 000000000..b4fed5e2c --- /dev/null +++ b/src/main/java/repository/VendingMachineRepository.java @@ -0,0 +1,32 @@ +package repository; + +import domain.Coin; +import domain.VendingMachine; + +import java.util.HashMap; +import java.util.Map; + +public class VendingMachineRepository { + private static final VendingMachineRepository vendingMachineRepository = new VendingMachineRepository(); + private VendingMachine vendingMachine; + + private VendingMachineRepository() { + vendingMachine = new VendingMachine(); + } + + public static VendingMachineRepository getInstance() { + return vendingMachineRepository; + } + + public void addCoins(Coin coin, int coinQuantity) { + vendingMachine.addCoins(coin, coinQuantity); + } + + public int findByCoin(Coin coin) { + return vendingMachine.findByCoin(coin); + } + + public Map findAll() { + return vendingMachine.findAll(); + } +} diff --git a/src/main/java/service/PaymentService.java b/src/main/java/service/PaymentService.java deleted file mode 100644 index 6c4403315..000000000 --- a/src/main/java/service/PaymentService.java +++ /dev/null @@ -1,14 +0,0 @@ -package service; - -import domain.Payment; -import dto.PaymentStatusDto; - -public class PaymentService { - public Payment createPayment(final String payment){ - return Payment.create(payment); - } - - public PaymentStatusDto createPaymentStatusDto(final Payment payment) { - return PaymentStatusDto.create(payment.getPayment()); - } -} diff --git a/src/main/java/service/ProductsService.java b/src/main/java/service/ProductsService.java index 435089618..2badeba25 100644 --- a/src/main/java/service/ProductsService.java +++ b/src/main/java/service/ProductsService.java @@ -1,9 +1,59 @@ package service; +import domain.Payment; +import domain.Product; import domain.Products; +import domain.wrapper.Name; +import dto.ProductNameDto; +import repository.ProductsRepository; +import repository.UserPaymentRepository; +import util.exception.NoMatchingCoinException; +import util.exception.NotEnoughBalanceException; +import util.message.ExceptionMessage; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; public class ProductsService { + + private final UserPaymentRepository userPaymentRepository = UserPaymentRepository.getInstance(); + private final ProductsRepository productsRepository = ProductsRepository.getInstance(); + public Products createProducts(final String productsInfo){ - return new Products(productsInfo); + return productsRepository.createProducts(productsInfo); + } + + public boolean checkSoldOutOfItemAvailableForBuy() { + Payment payment = userPaymentRepository.get(); + Products products = productsRepository.findAll(); + return products.getProducts().stream() + .filter(product -> Integer.valueOf(product.getPrice()) <= payment.getPayment()) + .allMatch(Product::isSoldOut); + } + + public Integer getMinItemPrice() { + Products products = productsRepository.findAll(); + List productPrices = products.getProducts().stream() + .map(Product::getPrice) + .collect(Collectors.toList()); + + return Collections.min(productPrices); } + + public void buyProduct(ProductNameDto productNameDto) { + Name productName = productNameDto.toEntity(); + Product product = productsRepository.findByProductName(productName); + + Payment userPayment = userPaymentRepository.get(); + if (!userPayment.canBuy(product)) { + throw new NotEnoughBalanceException(); + } + + Product purchasedProduct = product.decreaseQuantity(); + + userPaymentRepository.update(userPayment.subtract(product.getPrice())); + productsRepository.updateByItemName(productName, purchasedProduct); + } + } diff --git a/src/main/java/service/SelectedProductService.java b/src/main/java/service/SelectedProductService.java deleted file mode 100644 index 22ffeec0e..000000000 --- a/src/main/java/service/SelectedProductService.java +++ /dev/null @@ -1,11 +0,0 @@ -package service; - -import domain.PossessionAmount; -import domain.Products; -import domain.SelectedProduct; - -public class SelectedProductService { - public SelectedProduct createSelectedProduct(final String wantedProduct, final Products products){ - return SelectedProduct.create(wantedProduct, products); - } -} diff --git a/src/main/java/service/UserPaymentService.java b/src/main/java/service/UserPaymentService.java new file mode 100644 index 000000000..74c7c179b --- /dev/null +++ b/src/main/java/service/UserPaymentService.java @@ -0,0 +1,22 @@ +package service; + +import domain.Payment; +import dto.PaymentStatusDto; +import repository.UserPaymentRepository; + +public class UserPaymentService { + + private final UserPaymentRepository userPaymentRepository = UserPaymentRepository.getInstance(); + + public Payment createPayment(final String payment){ + return userPaymentRepository.createPayment(payment); + } + + public PaymentStatusDto createPaymentStatusDto(final Payment payment) { + return userPaymentRepository.createPaymentStatusDto(payment); + } + + public Payment getUserPayment(){ + return userPaymentRepository.get(); + } +} diff --git a/src/main/java/service/VendingMachineService.java b/src/main/java/service/VendingMachineService.java index 52fd60347..de1d0b694 100644 --- a/src/main/java/service/VendingMachineService.java +++ b/src/main/java/service/VendingMachineService.java @@ -1,36 +1,65 @@ package service; -import domain.Coin; -import domain.CoinCountGenerator; -import domain.VendingMachine; +import domain.*; +import dto.CoinsDto; import dto.VendingMachineStatusDto; +import repository.UserPaymentRepository; +import repository.VendingMachineRepository; +import view.OutputView; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class VendingMachineService { - private final VendingMachine vendingMachine; - private final CoinCountGenerator coinCountGenerator; + private static final int REMAINING_BALANCE_WHEN_ALL_COINS_GENERATED = 0; - public VendingMachineService(final VendingMachine vendingMachine, final CoinCountGenerator coinCountGenerator) { - this.vendingMachine = vendingMachine; - this.coinCountGenerator = coinCountGenerator; + private final VendingMachineRepository vendingMachineRepository = VendingMachineRepository.getInstance(); + + private final UserPaymentRepository userPaymentRepository = UserPaymentRepository.getInstance(); + + public CoinsDto getCurrentCoins() { + Map coins = vendingMachineRepository.findAll(); + return CoinsDto.from(coins); + } + + public void generateRandomCoins(int possessionAmount) { + + while (possessionAmount > REMAINING_BALANCE_WHEN_ALL_COINS_GENERATED) { + Coin randomCoin = Coin.pickRandomWithLimit(possessionAmount); + + vendingMachineRepository.addCoins(randomCoin, 1); + + possessionAmount = possessionAmount - randomCoin.getAmount(); + } } - public List generateRandomCoins(int amount) { - List statusList = new ArrayList<>(); + public CoinsDto getChange() { + Map changeCoins = new HashMap<>(); + int remainingBalance = userPaymentRepository.get().getPayment(); for (Coin coin : Coin.values()) { - CoinCountGenerator coinCountGenerator = new CoinCountGenerator(); - int coinCount = coinCountGenerator.generateRandomCoins(coin,amount); - vendingMachine.addCoins(coin, coinCount); - statusList.add(VendingMachineStatusDto.create(coin.getAmount(), coinCount)); - amount -= coin.getAmount() * coinCount; + int quantity = getCoinQuantityForChange(coin, remainingBalance); + changeCoins.put(coin, quantity); + + remainingBalance = remainingBalance - (coin.getAmount() * quantity); } - return statusList; + return CoinsDto.from(changeCoins); } + private int getCoinQuantityForChange(Coin coin, int balance) { + int maxCoinQuantityForChange = getMaxCoinQuantityForChange(coin, balance); + int holdingQuantity = vendingMachineRepository.findByCoin(coin); + + return Math.min(maxCoinQuantityForChange, holdingQuantity); + } + + private int getMaxCoinQuantityForChange(Coin coin, int balance) { + return balance / coin.getAmount(); + } } + diff --git a/src/main/java/util/CoinGenerator.java b/src/main/java/util/CoinGenerator.java deleted file mode 100644 index e4869117d..000000000 --- a/src/main/java/util/CoinGenerator.java +++ /dev/null @@ -1,7 +0,0 @@ -package util; - -import domain.Coin; - -public interface CoinGenerator { - int generateRandomCoins(Coin coin, int amount); -} diff --git a/src/main/java/util/exception/NotEnoughBalanceException.java b/src/main/java/util/exception/NotEnoughBalanceException.java new file mode 100644 index 000000000..7ffe3db4f --- /dev/null +++ b/src/main/java/util/exception/NotEnoughBalanceException.java @@ -0,0 +1,8 @@ +package util.exception; + +public class NotEnoughBalanceException extends IllegalArgumentException{ + public static final String ERROR_MESSAGE = "해당 상품을 구매하기에 잔여 투입 금액이 부족합니다."; + public NotEnoughBalanceException() { + super(ERROR_MESSAGE); + } +} diff --git a/src/main/java/util/exception/SoldOutException.java b/src/main/java/util/exception/SoldOutException.java new file mode 100644 index 000000000..a0894ceae --- /dev/null +++ b/src/main/java/util/exception/SoldOutException.java @@ -0,0 +1,9 @@ +package util.exception; + +public class SoldOutException extends IllegalArgumentException{ + + public static final String ERROR_MESSAGE = "품절된 상품은 구매할 수 없습니다."; + public SoldOutException() { + super(ERROR_MESSAGE); + } +} diff --git a/src/main/java/util/message/InputMessage.java b/src/main/java/util/message/InputMessage.java index c5f505554..7cc6a79e8 100644 --- a/src/main/java/util/message/InputMessage.java +++ b/src/main/java/util/message/InputMessage.java @@ -7,7 +7,7 @@ public enum InputMessage implements EnumUtil { INPUT_POSSESSION_AMOUNT_MESSAGE("자판기가 보유하고 있는 금액을 입력해 주세요."), INPUT_PRODUCT_DETAIL("\n상품명과 가격, 수량을 입력해 주세요."), INPUT_PAYMENT("\n투입 금액을 입력해 주세요."), - INPUT_SELECTED_PRODUCT("구매할 상품명을 입력해 주세요.\n"); + INPUT_SELECTED_PRODUCT("구매할 상품명을 입력해 주세요."); private final String message; diff --git a/src/main/java/util/message/OutputMessage.java b/src/main/java/util/message/OutputMessage.java index ca27a1237..7d0ad30cd 100644 --- a/src/main/java/util/message/OutputMessage.java +++ b/src/main/java/util/message/OutputMessage.java @@ -3,12 +3,18 @@ import util.EnumUtil; public enum OutputMessage implements EnumUtil { - - WON("원"), - COUNT("개"), - HYPHEN(" - "), VENDING_MACHINE_STATUS("\n자판기가 보유한 동전\n"), - PAYMENT_AMOUNT("\n투입 금액 : "); + PAYMENT_AMOUNT("\n투입 금액 : %d원"), + OUT_OF_STOCK_MESSAGE("해당 상품은 품절되었습니다.\n"), + INSUFFICIENT_FUNDS_MESSAGE("지불 금액이 부족합니다.\n"), + COIN_OUTPUT_FORMAT("%s - %s개"), + ERROR_MESSAGE_PREFIX("[ERROR ]"), + CHANGE_MESSAGE("잔돈"), + NO_CHANGE_MESSAGE("반환할 잔돈이 없습니다."), + COIN_500("500원"), + COIN_100("100원"), + COIN_50("50원"), + COIN_10("10원"); private final String message; diff --git a/src/main/java/vendingmachine/Application.java b/src/main/java/vendingmachine/Application.java index 500fb4834..f17111b70 100644 --- a/src/main/java/vendingmachine/Application.java +++ b/src/main/java/vendingmachine/Application.java @@ -1,10 +1,38 @@ package vendingmachine; +import controller.ProductsController; +import controller.UserPaymentController; import controller.VendingMachineController; +import view.InputView; +import view.OutputView; public class Application { + private static final VendingMachineController vendingMachineController = new VendingMachineController(); + private static final ProductsController productsController = new ProductsController(); + private static final UserPaymentController userPaymentController = new UserPaymentController(); + private static final InputView inputView = new InputView(); + private static final OutputView outputView = new OutputView(); + public static void main(String[] args) { - VendingMachineController vendingMachineController = new VendingMachineController(); - vendingMachineController.start(); + init(inputView, outputView); + purchaseProduct(inputView); + printChange(); + } + + private static void init(InputView inputView, OutputView outputView){ + vendingMachineController.generateCoins(inputView, outputView); + vendingMachineController.printGeneratedCoins(); + productsController.initProductInfo(inputView, outputView); + userPaymentController.generateUserBalance(inputView,outputView); + } + + private static void purchaseProduct(InputView inputView){ + while (productsController.checkAvailableToPurchase()) { + productsController.buyProduct(inputView); + } + } + + private static void printChange() { + vendingMachineController.printChange(); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 03fe528f1..464cd9399 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,26 +1,63 @@ package view; +import domain.constant.Constant; +import dto.CoinsDto; import dto.PaymentStatusDto; -import dto.VendingMachineStatusDto; import util.message.OutputMessage; -import java.util.List; - +import static com.sun.javafx.font.FontResource.ZERO; +import static domain.Coin.*; import static util.message.OutputMessage.*; +import static util.message.OutputMessage.COIN_10; +import static util.message.OutputMessage.COIN_100; +import static util.message.OutputMessage.COIN_50; +import static util.message.OutputMessage.COIN_500; public class OutputView { public static void printMessage(String message) { System.out.println(message); } - public void printVendingMachineStatus(List statusList) { - System.out.println(VENDING_MACHINE_STATUS.getValue()); - for (VendingMachineStatusDto statusDto : statusList) { - System.out.println(String.format("%d" + WON.getValue() + HYPHEN.getValue() + "%d" + COUNT.getValue(), statusDto.getCoin(), statusDto.getCount())); - } + private static void printCoin(String type, int coinQuantity) { + System.out.println(String.format(COIN_OUTPUT_FORMAT.getValue(), type, coinQuantity)); } public void printPaymentStatus(PaymentStatusDto paymentStatusDto){ - System.out.println(PAYMENT_AMOUNT.getValue() + paymentStatusDto.getPayment() + WON.getValue()); + System.out.println(String.format(PAYMENT_AMOUNT.getValue(), paymentStatusDto.getPayment())); + } + + public static void printCurrentUserBalance(int userBalance) { + System.out.println(String.format(PAYMENT_AMOUNT.getValue(), userBalance)); + } + + public static void printError(String message) { + System.out.println(ERROR_MESSAGE_PREFIX + message); + } + + public static void printChange(CoinsDto coinsDto) { + System.out.println(CHANGE_MESSAGE.getValue()); + + printCoinIgnoringZero(COIN_500.getValue(), coinsDto.getCoin500Quantity()); + printCoinIgnoringZero(COIN_100.getValue(), coinsDto.getCoin100Quantity()); + printCoinIgnoringZero(COIN_50.getValue(), coinsDto.getCoin50Quantity()); + printCoinIgnoringZero(COIN_10.getValue(), coinsDto.getCoin10Quantity()); + + if (coinsDto.getAllCoinQuantity() == ZERO) { + System.out.println(NO_CHANGE_MESSAGE.getValue()); + } + } + + private static void printCoinIgnoringZero(String type, int coinQuantity) { + if (coinQuantity > Constant.ZERO.getValue()) { + System.out.println(String.format(COIN_OUTPUT_FORMAT.getValue(), type, coinQuantity)); + } + } + + public static void printVendingMachineHoldingCoins(CoinsDto coinsDto) { + System.out.println(VENDING_MACHINE_STATUS.getValue()); + printCoin(COIN_500.getValue(), coinsDto.getCoin500Quantity()); + printCoin(COIN_100.getValue(), coinsDto.getCoin100Quantity()); + printCoin(COIN_50.getValue(), coinsDto.getCoin50Quantity()); + printCoin(COIN_10.getValue(), coinsDto.getCoin10Quantity()); } } diff --git a/src/test/java/domain/wrapper/ProductQuantityTest.java b/src/test/java/domain/wrapper/ProductQuantityTest.java index f99355100..68cc72366 100644 --- a/src/test/java/domain/wrapper/ProductQuantityTest.java +++ b/src/test/java/domain/wrapper/ProductQuantityTest.java @@ -10,7 +10,6 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static util.message.ExceptionMessage.*; -import static util.message.ExceptionMessage.TEN_UNIT_MESSAGE; public class ProductQuantityTest { @ParameterizedTest diff --git a/src/test/java/domain/wrapper/SelectedProductNameTest.java b/src/test/java/domain/wrapper/SelectedProductNameTest.java deleted file mode 100644 index 874a9c3cc..000000000 --- a/src/test/java/domain/wrapper/SelectedProductNameTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package domain.wrapper; - -import domain.Products; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EmptySource; -import org.junit.jupiter.params.provider.ValueSource; -import provider.TestProvider; -import util.exception.NoResourceException; - -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static util.message.ExceptionMessage.BLANK_MESSAGE; -import static util.message.ExceptionMessage.NO_RESOURCE_MESSAGE; - -public class SelectedProductNameTest { - - private Products testProducts; - - @BeforeEach - void init() { - String testProductInfos = "[콜라,1500,20];[사이다,1000,10]"; - testProducts = TestProvider.createTestProducts(testProductInfos); - } - - @ParameterizedTest - @DisplayName("정상적으로 입력했을 경우 예외를 처리하지 않는다.") - @ValueSource(strings = {"콜라", "사이다"}) - void givenNormalName_thenSuccess(final String name) { - assertThat(SelectedProductName.create(name, testProducts)) - .isInstanceOf(SelectedProductName.class); - - assertThatCode(() -> SelectedProductName.create(name, testProducts)) - .doesNotThrowAnyException(); - } - - @ParameterizedTest - @DisplayName("구매할 상품명이 공백일 경우 예외를 처리한다.") - @EmptySource - void givenBlankName_thenFail(final String name) { - assertThatThrownBy(() -> SelectedProductName.create(name, testProducts)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(String.format(BLANK_MESSAGE.getValue(), "구매할 상품명")); - } - - @ParameterizedTest - @DisplayName("구매할 상품명이 자판기에 없는 경우 예외가 발생한다.") - @ValueSource(strings = {"자두", "케익"}) - void givenBlankPaymentAmount_thenFail(final String productName) { - assertThatThrownBy(() -> SelectedProductName.create(productName, testProducts)) - .isInstanceOf(NoResourceException.class) - .hasMessageContaining(String.format(NO_RESOURCE_MESSAGE.getValue(), "해당 상품")); - } -} diff --git a/src/test/java/service/VendingMachineServiceTest.java b/src/test/java/service/VendingMachineServiceTest.java deleted file mode 100644 index f925d95f9..000000000 --- a/src/test/java/service/VendingMachineServiceTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package service; - -import domain.Coin; -import domain.CoinCountGenerator; -import domain.VendingMachine; -import dto.VendingMachineStatusDto; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.*; - -public class VendingMachineServiceTest { - - @Test - void generateRandomCoins_ShouldGenerateRandomCoins() { - VendingMachine vendingMachine = new VendingMachine(); - CoinCountGenerator coinCountGenerator = new CoinCountGenerator(); - VendingMachineService vendingMachineService = new VendingMachineService(vendingMachine, coinCountGenerator); - - int userAmount = 450; - List result = vendingMachineService.generateRandomCoins(userAmount); - - assertEquals(4, result.size()); - - int totalAmount = 0; - for (VendingMachineStatusDto status : result) { - totalAmount += status.getCoin() * status.getCount(); - } - - // 생성된 동전의 총 금액이 사용자가 입력한 금액과 일치하는지 확인 - assertEquals(userAmount, totalAmount); - } - - -} From 25921a66d8577d958d0c34d006d2b0358a7c608b Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 28 Nov 2023 20:49:37 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor:=20Console=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/controller/ProductsController.java | 34 +++++++++---------- .../controller/UserPaymentController.java | 27 ++++++--------- .../controller/VendingMachineController.java | 24 +++++-------- .../java/util/message/ExceptionMessage.java | 2 -- src/main/java/util/message/OutputMessage.java | 7 ++-- src/main/java/vendingmachine/Application.java | 20 +++++------ src/main/java/view/InputView.java | 27 ++------------- src/main/java/view/OutputView.java | 6 +--- 8 files changed, 49 insertions(+), 98 deletions(-) diff --git a/src/main/java/controller/ProductsController.java b/src/main/java/controller/ProductsController.java index a328e5b2f..7b2592dd4 100644 --- a/src/main/java/controller/ProductsController.java +++ b/src/main/java/controller/ProductsController.java @@ -22,32 +22,34 @@ public ProductsController(){ userPaymentService = new UserPaymentService(); } - public void initProductInfo(InputView inputView, OutputView outputView){ - String productInfo = getProductInfo(inputView); - products = createProducts(productInfo); + public void generateProductInfo(){ + String productInfo = getProductInfo(); + try{ + products = createProducts(productInfo); + } catch (IllegalArgumentException e){ + OutputView.printMessage(e.getMessage()); + generateProductInfo(); + } } - private String getProductInfo(InputView inputView){ - return inputView.getUserInput(() -> { - OutputView.printMessage(INPUT_PRODUCT_DETAIL.getValue()); - return inputView.readConsole(); - }); + private String getProductInfo(){ + return InputView.readConsole(); } private Products createProducts(String productInfo){ return productsService.createProducts(productInfo); } - public void buyProduct(InputView inputView){ - String wantedProduct = getSelectedProduct(inputView); + public void buyProduct(){ + String wantedProduct = getSelectedProduct(); ProductNameDto productNameDto = createSelectedProduct(wantedProduct); try{ productsService.buyProduct(productNameDto); Payment payment = userPaymentService.getUserPayment(); OutputView.printCurrentUserBalance(payment.getPayment()); } catch(IllegalArgumentException e){ - OutputView.printError(e.getMessage()); - buyProduct(inputView); + OutputView.printMessage(e.getMessage()); + buyProduct(); } } @@ -55,11 +57,9 @@ private ProductNameDto createSelectedProduct(String wantedProduct){ return ProductNameDto.create(wantedProduct); } - private String getSelectedProduct(InputView inputView){ - return inputView.getUserInput(() -> { - OutputView.printMessage(INPUT_SELECTED_PRODUCT.getValue()); - return inputView.readConsole(); - }); + private String getSelectedProduct(){ + OutputView.printMessage(INPUT_SELECTED_PRODUCT.getValue()); + return InputView.readConsole(); } public boolean checkAvailableToPurchase() { diff --git a/src/main/java/controller/UserPaymentController.java b/src/main/java/controller/UserPaymentController.java index 6e5546788..8020ff2df 100644 --- a/src/main/java/controller/UserPaymentController.java +++ b/src/main/java/controller/UserPaymentController.java @@ -15,28 +15,21 @@ public UserPaymentController(){ userPaymentService = new UserPaymentService(); } - public void generateUserBalance(InputView inputView, OutputView outputView) { - + public void generateUserBalance() { + String paymentAmount = getPayment(); try { - initUserPayment(inputView, outputView); + Payment payment = createPayment(paymentAmount); + PaymentStatusDto paymentStatusDto = userPaymentService.createPaymentStatusDto(payment); + OutputView.printPaymentStatus(paymentStatusDto); } catch (IllegalArgumentException e) { - OutputView.printError(e.getMessage()); - generateUserBalance(inputView, outputView); + OutputView.printMessage(e.getMessage()); + generateUserBalance(); } } - public void initUserPayment(InputView inputView, OutputView outputView){ - String paymentAmount = getPayment(inputView); - Payment payment = createPayment(paymentAmount); - PaymentStatusDto paymentStatusDto = userPaymentService.createPaymentStatusDto(payment); - outputView.printPaymentStatus(paymentStatusDto); - } - - private String getPayment(InputView inputView){ - return inputView.getUserInput(() -> { - OutputView.printMessage(INPUT_PAYMENT.getValue()); - return inputView.readConsole(); - }); + private String getPayment(){ + OutputView.printMessage(INPUT_PAYMENT.getValue()); + return InputView.readConsole(); } private Payment createPayment(String paymentAmount){ diff --git a/src/main/java/controller/VendingMachineController.java b/src/main/java/controller/VendingMachineController.java index df72db72e..59e287dfb 100644 --- a/src/main/java/controller/VendingMachineController.java +++ b/src/main/java/controller/VendingMachineController.java @@ -20,14 +20,15 @@ public VendingMachineController(){ vendingMachineService = new VendingMachineService(); } - public void generateCoins(InputView inputView, OutputView outputView) { - PossessionAmount possessionAmount = initPossessionAmount(inputView, outputView); + public void generateCoins() { + String amount = getPossessionAmount(); try { + PossessionAmount possessionAmount = createPossessionAmount(amount); initCoins(possessionAmount); } catch (IllegalArgumentException e) { - OutputView.printError(e.getMessage()); - generateCoins(inputView, outputView); + OutputView.printMessage(e.getMessage()); + generateCoins(); } } @@ -35,18 +36,9 @@ public void initCoins(PossessionAmount possessionAmount){ vendingMachineService.generateRandomCoins(possessionAmount.getPossessionAmount()); } - public PossessionAmount initPossessionAmount(InputView inputView, OutputView outputView){ - String amount = getPossessionAmount(inputView); - return createPossessionAmount(amount); - } - - - - private String getPossessionAmount(InputView inputView){ - return inputView.getUserInput(() -> { - OutputView.printMessage(INPUT_POSSESSION_AMOUNT_MESSAGE.getValue()); - return inputView.readConsole(); - }); + private String getPossessionAmount(){ + OutputView.printMessage(INPUT_POSSESSION_AMOUNT_MESSAGE.getValue()); + return InputView.readConsole(); } private PossessionAmount createPossessionAmount(String possessionAmount){ diff --git a/src/main/java/util/message/ExceptionMessage.java b/src/main/java/util/message/ExceptionMessage.java index 28d576881..289fb691a 100644 --- a/src/main/java/util/message/ExceptionMessage.java +++ b/src/main/java/util/message/ExceptionMessage.java @@ -4,8 +4,6 @@ public enum ExceptionMessage implements EnumUtil { BLANK_MESSAGE("%s은(는) 빈 값이 들어올 수 없습니다.") - , LENGTH_MESSAGE("%d글자를 초과하였습니다.") - , INPUT_MESSAGE("입력 중에 예기치 못한 오류가 발생하였습니다. 예외 메시지: %s") , TYPE_MESSAGE("%s은(는) 숫자만 입력할 수 있습니다.") , RANGE_MESSAGE("%d 보다 큰 값을 입력해 주세요.") , UNIT_MESSAGE("%d원 단위로 입력해 주세요.") diff --git a/src/main/java/util/message/OutputMessage.java b/src/main/java/util/message/OutputMessage.java index 7d0ad30cd..03d657efb 100644 --- a/src/main/java/util/message/OutputMessage.java +++ b/src/main/java/util/message/OutputMessage.java @@ -3,12 +3,9 @@ import util.EnumUtil; public enum OutputMessage implements EnumUtil { - VENDING_MACHINE_STATUS("\n자판기가 보유한 동전\n"), - PAYMENT_AMOUNT("\n투입 금액 : %d원"), - OUT_OF_STOCK_MESSAGE("해당 상품은 품절되었습니다.\n"), - INSUFFICIENT_FUNDS_MESSAGE("지불 금액이 부족합니다.\n"), + VENDING_MACHINE_STATUS("\n자판기가 보유한 동전"), + PAYMENT_AMOUNT("\n투입 금액: %d원"), COIN_OUTPUT_FORMAT("%s - %s개"), - ERROR_MESSAGE_PREFIX("[ERROR ]"), CHANGE_MESSAGE("잔돈"), NO_CHANGE_MESSAGE("반환할 잔돈이 없습니다."), COIN_500("500원"), diff --git a/src/main/java/vendingmachine/Application.java b/src/main/java/vendingmachine/Application.java index f17111b70..5ba022820 100644 --- a/src/main/java/vendingmachine/Application.java +++ b/src/main/java/vendingmachine/Application.java @@ -3,32 +3,28 @@ import controller.ProductsController; import controller.UserPaymentController; import controller.VendingMachineController; -import view.InputView; -import view.OutputView; public class Application { private static final VendingMachineController vendingMachineController = new VendingMachineController(); private static final ProductsController productsController = new ProductsController(); private static final UserPaymentController userPaymentController = new UserPaymentController(); - private static final InputView inputView = new InputView(); - private static final OutputView outputView = new OutputView(); public static void main(String[] args) { - init(inputView, outputView); - purchaseProduct(inputView); + init(); + purchaseProduct(); printChange(); } - private static void init(InputView inputView, OutputView outputView){ - vendingMachineController.generateCoins(inputView, outputView); + private static void init(){ + vendingMachineController.generateCoins(); vendingMachineController.printGeneratedCoins(); - productsController.initProductInfo(inputView, outputView); - userPaymentController.generateUserBalance(inputView,outputView); + productsController.generateProductInfo(); + userPaymentController.generateUserBalance(); } - private static void purchaseProduct(InputView inputView){ + private static void purchaseProduct(){ while (productsController.checkAvailableToPurchase()) { - productsController.buyProduct(inputView); + productsController.buyProduct(); } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 65ff9d2c9..b38330908 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,31 +1,10 @@ package view; -import util.exception.ConsoleException; -import util.exception.GlobalException; -import util.message.ExceptionMessage; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.function.Supplier; +import camp.nextstep.edu.missionutils.Console; public class InputView { - private final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); - - public String readConsole() { - try { - return bufferedReader.readLine(); - } catch (IOException e) { - throw new ConsoleException(String.format(ExceptionMessage.INPUT_MESSAGE.getValue(), e.getMessage())); - } - } - public T getUserInput(Supplier inputReader) { - try { - return inputReader.get(); - } catch (GlobalException | IllegalArgumentException e) { - OutputView.printMessage(e.getMessage()); - return getUserInput(inputReader); - } + public static String readConsole(){ + return Console.readLine(); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 464cd9399..7e1fbafc8 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -22,7 +22,7 @@ private static void printCoin(String type, int coinQuantity) { System.out.println(String.format(COIN_OUTPUT_FORMAT.getValue(), type, coinQuantity)); } - public void printPaymentStatus(PaymentStatusDto paymentStatusDto){ + public static void printPaymentStatus(PaymentStatusDto paymentStatusDto){ System.out.println(String.format(PAYMENT_AMOUNT.getValue(), paymentStatusDto.getPayment())); } @@ -30,10 +30,6 @@ public static void printCurrentUserBalance(int userBalance) { System.out.println(String.format(PAYMENT_AMOUNT.getValue(), userBalance)); } - public static void printError(String message) { - System.out.println(ERROR_MESSAGE_PREFIX + message); - } - public static void printChange(CoinsDto coinsDto) { System.out.println(CHANGE_MESSAGE.getValue());