diff --git a/README.md b/README.md index f5a9ecea20..46c44a54af 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,7 @@ ## 로또 미션을 위한 저장소 - --- - ## 게임 순서 - - 구입 금액을 입력 받는다. - - 1000단위가 아닌 경우는 예외처리 - 문자열이 포함되는 경우 예외처리 - 공백 및 NULL 예외처리 @@ -13,9 +9,7 @@ - 구입 금액 또한 클래스로 분리하고 - 기능은 생성자를 통한 검증 - 장수를 계산해서 보내는 부분도 - - 금액 / 1000 의 갯수만큼 로또를 자동으로 생성 및 출력한다. - - Number : 각각의 수 - 1~45 사이의 값인지 - 문자열이 포함되는 경우 예외처리 @@ -27,7 +21,6 @@ - Lotto를 구매한 만큼 가지고 있는 Lottos 객체를 생성한다. - 금액 / 1000 == Lottos.size() - 이렇게 생성된 Lottos 를 출력한다. - - 당첨번호(보너스 번호 제외) 6자리를 `,` 를 기준으로 분리한다. - 싱글톤 ? - 당첨번호를 담당하는 WinningLotto 객체를 생성한다. @@ -36,23 +29,18 @@ - 갯수 6개 - 번호의 범위 - 각각 다른 번호인지 - - 보너스 볼 하나를 입력한다 - 보너스 볼 객체 Bonus - Number 하나를 갖는 객체 - - 당첨 통계를 출력한다 - LottoModel ← Lottos , Winning - OutputView - Enum 에서 맞힌 갯수, 금액, 몇장인지를 가지는 Enum에서 처리 - - 참고 - 숫자를 생성할 때 그리고 보너스 넘버를 생성할 때 로직이 겹치니까 해결책으로 - Validator를 따로 둔다. - Number를 상속받는다. - ### 구현 순서 - - 구입 금액 테스트 - [x] 1000단위가 아닌 경우는 예외처리 - [x] 문자열이 포함되는 경우 예외처리 @@ -62,38 +50,40 @@ - Utility 클래스 검증 - [x] 공백 제거 - [x] `,` Parsing 검증 - - Number - [x] 1~45 사이의 값인지 - [x] 문자열이 포함되는 경우 예외처리 - [x] 공백 및 NULL 예외처리 - Enum을 통해서 Number 기능을 같이 하는 방안 고려 (현재 static 부분) - - LottoFactory - - Lotto - [x] `Lotto.size()` = 6 - [x] 중복된 수가 있으면 안된다. - - Lottos - [x] Lotto를 구매한 만큼 가지고 있는 Lottos 객체를 생성한다 - [x] 금액 / 1000 == Lottos.size() - - WinningLotto - [x] 보너스넘버와 WinningNumbers의 값이 겹치지 않아야 한다. - [x] BonusNumber (상속 : Number) - [x] WinningNumbers - [x] 상속받고 있고 (Lotto) : 로직이 같고, 로또와 의미를 구분하기 위함 - - [x] Result - 인자 : WinningLotto, Lottos - 기능 - [x] 맞힌 갯수별로 장수 + 수익금 계산 - [x] 수익률 - [x] OutputView 보내기 - +- 추가 요구사항(수동) + - [x] 수동으로 구매할 로또의 개수를 입력 받는다. + - [x] 개수라는 클래스를 두고 + - [x] 음수 예외처리 + - [x] 금액을 초과하는 경우 + - [x] 문자열 + - [x] Null & 공백 + - [x] 금액이랑 비교 + - [x] 수동으로 구매할 로또의 번호를 개수만큼 입력받는다. + - [x] 기존의 LottoFactory 를 재활용한다. ### 프로그래밍 요구사항 - - indent(인덴트, 들여쓰기) depth를 2단계에서 1단계로 줄여라. - depth의 경우 if문을 사용하는 경우 1단계의 depth가 증가한다. if문 안에 while문을 사용한다면 depth가 2단계가 된다. - else를 사용하지 마라. @@ -101,9 +91,12 @@ - method가 한 가지 일만 하도록 최대한 작게 만들어라. - **배열 대신 ArrayList를 사용한다.** - enum을 적용해 프로그래밍을 구현한다. - +- 예외가 발생하는 부분에 대해 자바 Exception을 적용해 예외처리한다. +- 사용자가 입력한 값에 대한 예외 처리를 철저히 한다. ### 힌트 - - 로또 자동 생성은 Collections.shuffle() 메소드 활용한다. - Collections.sort() 메소드를 활용해 정렬 가능하다. -- ArrayList의 contains() 메소드를 활용하면 어떤 값이 존재하는지 유무를 판단할 수 있다. \ No newline at end of file +- ArrayList의 contains() 메소드를 활용하면 어떤 값이 존재하는지 유무를 판단할 수 있다. +## 추가 요구 사항 +- 현재 로또 생성기는 자동 생성 기능만 제공한다. 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다. +- 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다. \ No newline at end of file diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java index 31ec9d9de3..f5c8470192 100644 --- a/src/main/java/lotto/domain/Lotto.java +++ b/src/main/java/lotto/domain/Lotto.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.stream.Collectors; import lotto.exception.InvalidLottoException; diff --git a/src/main/java/lotto/domain/LottoCount.java b/src/main/java/lotto/domain/LottoCount.java new file mode 100644 index 0000000000..0c8e5f9876 --- /dev/null +++ b/src/main/java/lotto/domain/LottoCount.java @@ -0,0 +1,37 @@ +package lotto.domain; + +import lotto.exception.ExceedMoneyException; +import lotto.util.StringUtil; + +public class LottoCount { + private final int manualLotto; + private final int autoLotto; + + public LottoCount(String manualLotto, int totalLotto) { + validate(manualLotto); + validateMoney(manualLotto, totalLotto); + this.manualLotto = Integer.parseInt(manualLotto); + this.autoLotto = totalLotto - this.manualLotto; + } + + private void validate(String ManualLotto) { + StringUtil.checkNull(ManualLotto); + StringUtil.checkBlank(ManualLotto); + StringUtil.checkNumberFormat(ManualLotto); + StringUtil.checkRange(ManualLotto); + } + + private void validateMoney(String input, int totalLotto) { + if (totalLotto < Integer.parseInt(input)) { + throw new ExceedMoneyException(totalLotto + "장 이하만 구매가 가능합니다."); + } + } + + public int getManualLottoCount() { + return manualLotto; + } + + public int getAutoLottoCount() { + return autoLotto; + } +} diff --git a/src/main/java/lotto/domain/LottoFactory.java b/src/main/java/lotto/domain/LottoFactory.java index da2894e7b5..4ad8839ad0 100644 --- a/src/main/java/lotto/domain/LottoFactory.java +++ b/src/main/java/lotto/domain/LottoFactory.java @@ -1,11 +1,14 @@ package lotto.domain; -import java.util.*; +import lotto.util.StringUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import lotto.util.StringUtil; - import static lotto.domain.Number.MAX_LOTTO_NUMBER; import static lotto.domain.Number.MIN_LOTTO_NUMBER; @@ -65,9 +68,27 @@ public static Lotto create(String winningNumbers) { */ public static Lottos create(int count) { List lottos = new ArrayList<>(); - for (int i = 0; i < count; i++) { + addAutoLottos(count, lottos); + return new Lottos(lottos); + } + + public static Lottos create(List manualLottoNumbers, int autoCount) { + List lottos = addManualLottos(manualLottoNumbers); + addAutoLottos(autoCount, lottos); + return new Lottos(lottos); + } + + private static void addAutoLottos(int autoCount, List lottos) { + for (int i = 0; i < autoCount; i++) { lottos.add(create()); } - return new Lottos(lottos); + } + + private static List addManualLottos(List manualLottoNumbers) { + List lottos = new ArrayList<>(); + for (String input : manualLottoNumbers) { + lottos.add(create(input)); + } + return lottos; } } diff --git a/src/main/java/lotto/domain/Number.java b/src/main/java/lotto/domain/Number.java index 7f7159dd42..d5c954a410 100644 --- a/src/main/java/lotto/domain/Number.java +++ b/src/main/java/lotto/domain/Number.java @@ -7,6 +7,7 @@ import java.util.Objects; import lotto.exception.InvalidNumberException; +import lotto.util.StringUtil; import static javax.management.Query.value; @@ -26,20 +27,12 @@ public Number(int number) { } public static void validate(String value) { - checkNull(value); - checkBlank(value); - checkNumberFormat(value); + StringUtil.checkNull(value); + StringUtil.checkBlank(value); + StringUtil.checkNumberFormat(value); checkRange(value); } - private static void checkNumberFormat(String value) { - try { - Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new InvalidNumberException("문자는 사용이 불가능합니다."); - } - } - private static void checkRange(String value) { int number = Integer.parseInt(value); if (number > MAX_LOTTO_NUMBER || number < MIN_LOTTO_NUMBER) { @@ -47,18 +40,6 @@ private static void checkRange(String value) { } } - private static void checkBlank(String value) { - if (value.trim().isEmpty()) { - throw new InvalidNumberException("공백은 사용이 불가능합니다."); - } - } - - private static void checkNull(String value) { - if (Objects.isNull(value)) { - throw new InvalidNumberException("Null문자열은 사용이 불가능합니다."); - } - } - @Override public String toString() { return Integer.toString(number); diff --git a/src/main/java/lotto/domain/controller/LottoController.java b/src/main/java/lotto/domain/controller/LottoController.java index 8df5576230..03c07db706 100644 --- a/src/main/java/lotto/domain/controller/LottoController.java +++ b/src/main/java/lotto/domain/controller/LottoController.java @@ -1,22 +1,24 @@ package lotto.domain.controller; -import lotto.domain.Lotto; -import lotto.domain.LottoFactory; -import lotto.domain.Lottos; import lotto.domain.Number; -import lotto.domain.PurchaseMoney; -import lotto.domain.WinningLotto; +import lotto.domain.*; import lotto.domain.model.LottoGame; import lotto.domain.result.GameResult; import lotto.view.InputView; import lotto.view.OutputView; +import java.util.List; + public class LottoController { public void run() { PurchaseMoney money = new PurchaseMoney(InputView.getMoney()); - Lottos lottos = LottoFactory.create(money.parseToPiece()); - OutputView.printPieces(money.parseToPiece()); + LottoCount count = new LottoCount(InputView.getManualLottoCount(), money.parseToPiece()); + List manual = InputView.getManualLottosNumber(count.getManualLottoCount()); + Lottos lottos = LottoFactory.create(manual, count.getAutoLottoCount()); + + OutputView.printPieces(count.getManualLottoCount(), count.getAutoLottoCount()); OutputView.printLottos(lottos); + createResult(money, lottos, createWinningLotto()); } diff --git a/src/main/java/lotto/domain/result/Count.java b/src/main/java/lotto/domain/result/Count.java new file mode 100644 index 0000000000..20df444fc1 --- /dev/null +++ b/src/main/java/lotto/domain/result/Count.java @@ -0,0 +1,13 @@ +package lotto.domain.result; + +public class Count { + private int count = 0; + + public void addCount() { + count++; + } + + public int getCount() { + return count; + } +} diff --git a/src/main/java/lotto/domain/result/Rank.java b/src/main/java/lotto/domain/result/Rank.java index 05e8d322d3..4d578c2aaa 100644 --- a/src/main/java/lotto/domain/result/Rank.java +++ b/src/main/java/lotto/domain/result/Rank.java @@ -21,9 +21,9 @@ public enum Rank { } public static Rank getRank(int numberOfMatch, boolean isBonus) { - return Arrays.stream(values()) + return Arrays.stream(values()) .filter(rank -> rank.matchingNumbers == numberOfMatch) - .filter(rank->rank.checkBonus(isBonus)) + .filter(rank -> rank.checkBonus(isBonus)) .findFirst() .orElse(DEFAULT); } @@ -47,10 +47,7 @@ public boolean checkBonus(boolean isBonus) { return true; } - public boolean isBonus(){ - if(this==BONUS){ - return true; - } - return false; + public boolean isBonus() { + return this == BONUS; } } diff --git a/src/main/java/lotto/exception/EmptyInputException.java b/src/main/java/lotto/exception/EmptyInputException.java new file mode 100644 index 0000000000..18187051ff --- /dev/null +++ b/src/main/java/lotto/exception/EmptyInputException.java @@ -0,0 +1,7 @@ +package lotto.exception; + +public class EmptyInputException extends RuntimeException { + public EmptyInputException(String message) { + super(message); + } +} diff --git a/src/main/java/lotto/exception/ExceedMoneyException.java b/src/main/java/lotto/exception/ExceedMoneyException.java new file mode 100644 index 0000000000..15c1b53bd0 --- /dev/null +++ b/src/main/java/lotto/exception/ExceedMoneyException.java @@ -0,0 +1,7 @@ +package lotto.exception; + +public class ExceedMoneyException extends RuntimeException { + public ExceedMoneyException(String message) { + super(message); + } +} diff --git a/src/main/java/lotto/exception/InvalidRangeException.java b/src/main/java/lotto/exception/InvalidRangeException.java new file mode 100644 index 0000000000..14a7fffdb0 --- /dev/null +++ b/src/main/java/lotto/exception/InvalidRangeException.java @@ -0,0 +1,7 @@ +package lotto.exception; + +public class InvalidRangeException extends RuntimeException { + public InvalidRangeException(String message) { + super(message); + } +} diff --git a/src/main/java/lotto/util/StringUtil.java b/src/main/java/lotto/util/StringUtil.java index f19c13ed58..592fd23ac5 100644 --- a/src/main/java/lotto/util/StringUtil.java +++ b/src/main/java/lotto/util/StringUtil.java @@ -1,18 +1,48 @@ package lotto.util; +import lotto.exception.EmptyInputException; +import lotto.exception.InvalidRangeException; + import java.util.Arrays; import java.util.List; +import java.util.Objects; public class StringUtil { - public static final String BLANK = " "; - public static final String NO_BLANK = ""; - public static final String DELIMITER = ","; + public static final String BLANK = " "; + public static final String NO_BLANK = ""; + public static final String DELIMITER = ","; + + public static String removeBlank(String value) { + return value.replaceAll(BLANK, NO_BLANK); + } + + public static List parseByComma(String value) { + return Arrays.asList(value.split(DELIMITER)); + } + + public static void checkBlank(String input) { + if (input.trim().isEmpty()) { + throw new EmptyInputException("공백은 사용할 수 없습니다."); + } + } + + public static void checkNull(Object o) { + if (Objects.isNull(o)) { + throw new NullPointerException("널문자는 사용할 수 없습니다."); + } + } - public static String removeBlank(String value) { - return value.replaceAll(BLANK, NO_BLANK); - } + public static void checkNumberFormat(String input) { + try { + Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new NumberFormatException("문자열은 사용할 수 없습니다."); + } + } - public static List parseByComma(String value) { - return Arrays.asList(value.split(DELIMITER)); - } + public static void checkRange(String input) { + if (Integer.parseInt(input) < 0) { + throw new InvalidRangeException("음수는 입력할 수 없습니다."); + } + } } diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java index af50722965..1115cc5fdf 100644 --- a/src/main/java/lotto/view/InputView.java +++ b/src/main/java/lotto/view/InputView.java @@ -1,5 +1,7 @@ package lotto.view; +import java.util.ArrayList; +import java.util.List; import java.util.Scanner; public class InputView { @@ -19,4 +21,18 @@ public static String getBonusNumber() { System.out.println("보너스 번호를 입력해 주세요."); return scanner.nextLine(); } + + public static String getManualLottoCount() { + System.out.println("수동으로 구매할 로또 수를 입력해 주세요."); + return scanner.nextLine(); + } + + public static List getManualLottosNumber(int manualLottoCount) { + List input = new ArrayList<>(); + System.out.println("수동으로 구매할 번호를 입력해 주세요."); + for (int i = 0; i < manualLottoCount; i++) { + input.add(scanner.nextLine()); + } + return input; + } } diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java index 36150f6b65..ed5673f7eb 100644 --- a/src/main/java/lotto/view/OutputView.java +++ b/src/main/java/lotto/view/OutputView.java @@ -10,8 +10,8 @@ import lotto.domain.result.Rank; public class OutputView { - public static void printPieces(int parseToPiece) { - System.out.println(parseToPiece + "개를 구매했습니다."); + public static void printPieces(int manualCount, int autoCount) { + System.out.println("수동으로 " +manualCount+ "장, 자동으로 "+autoCount+"개를 구매했습니다."); } public static void printLottos(Lottos lottos) { diff --git a/src/test/java/lotto/domain/LottoCountTest.java b/src/test/java/lotto/domain/LottoCountTest.java new file mode 100644 index 0000000000..5bc05ce74c --- /dev/null +++ b/src/test/java/lotto/domain/LottoCountTest.java @@ -0,0 +1,51 @@ +package lotto.domain; + +import lotto.exception.ExceedMoneyException; +import lotto.exception.InvalidRangeException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class LottoCountTest { + private PurchaseMoney money; + + @BeforeEach + void init() { + this.money = new PurchaseMoney("15000"); + } + + @ParameterizedTest + @ValueSource(strings = {"-1", "-3"}) + void negativeInputTest(String input) { + assertThatThrownBy(() -> { + new LottoCount(input, money.parseToPiece()); + }).isInstanceOf(InvalidRangeException.class) + .hasMessageMatching("음수는 입력할 수 없습니다."); + } + + @Test + void overNumberManual() { + assertThatThrownBy(() -> { + new LottoCount("16", money.parseToPiece()); + }).isInstanceOf(ExceedMoneyException.class) + .hasMessageMatching("15장 이하만 구매가 가능합니다."); + } + + @Test + void manualCountTest() { + assertThat(new LottoCount("2", money.parseToPiece()) + .getManualLottoCount()) + .isEqualTo(2); + } + + @Test + void autoCountTest() { + assertThat(new LottoCount("2", money.parseToPiece()) + .getAutoLottoCount()) + .isEqualTo(13); + } +} diff --git a/src/test/java/lotto/domain/LottoFactoryTest.java b/src/test/java/lotto/domain/LottoFactoryTest.java index f116969447..eb0ed472f8 100644 --- a/src/test/java/lotto/domain/LottoFactoryTest.java +++ b/src/test/java/lotto/domain/LottoFactoryTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.*; import java.util.Arrays; +import java.util.Iterator; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -47,5 +48,17 @@ void makeLottoManually(String input, int expected) { void makeLottosTest() { Lottos lottos = LottoFactory.create(3); assertThat(lottos.getSize()).isEqualTo(3); + + } + + @Test + void makeManualLottosTest(){ + List manual = Arrays.asList("1,2,3,4,5,6", "3,4,5,6,7,8"); + Lottos lottos = LottoFactory.create(manual, 10); + Lotto firstLotto = LottoFactory.create("1,2,3,4,5,6"); + Lotto secondLotto = LottoFactory.create("3,4,5,6,7,8"); + Iterator lottoIterator = lottos.iterator(); + assertThat(lottoIterator.next().compare(firstLotto)).isEqualTo(6); + assertThat(lottoIterator.next().compare(secondLotto)).isEqualTo(6); } } \ No newline at end of file diff --git a/src/test/java/lotto/domain/NumberTest.java b/src/test/java/lotto/domain/NumberTest.java index 33531e75f6..623d875f16 100644 --- a/src/test/java/lotto/domain/NumberTest.java +++ b/src/test/java/lotto/domain/NumberTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.*; +import lotto.exception.EmptyInputException; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -32,22 +33,22 @@ void rangeOver(String value) { @DisplayName("수를 문자열로 입력하는 경우") void numberFormatTest(String value) { assertThatThrownBy(() -> new Number(value)) - .isInstanceOf(InvalidNumberException.class) - .hasMessageMatching("문자는 사용이 불가능합니다."); + .isInstanceOf(NumberFormatException.class) + .hasMessageMatching("문자열은 사용할 수 없습니다."); } @ParameterizedTest @ValueSource(strings = {"", " "}) void 공백_테스트(String value) { assertThatThrownBy(() -> new Number(value)) - .isInstanceOf(InvalidNumberException.class) - .hasMessageMatching("공백은 사용이 불가능합니다."); + .isInstanceOf(EmptyInputException.class) + .hasMessageMatching("공백은 사용할 수 없습니다."); } @Test void NULL_테스트() { assertThatThrownBy(() -> new Number(null)) - .isInstanceOf(InvalidNumberException.class) - .hasMessageMatching("Null문자열은 사용이 불가능합니다."); + .isInstanceOf(NullPointerException.class) + .hasMessageMatching("널문자는 사용할 수 없습니다."); } } diff --git a/src/test/java/lotto/util/StringUtilTest.java b/src/test/java/lotto/util/StringUtilTest.java index 3f98aa6564..6d3fe41d14 100644 --- a/src/test/java/lotto/util/StringUtilTest.java +++ b/src/test/java/lotto/util/StringUtilTest.java @@ -1,21 +1,44 @@ package lotto.util; -import static org.assertj.core.api.Assertions.*; - +import lotto.exception.EmptyInputException; +import org.junit.jupiter.api.Test; 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.assertThatThrownBy; + public class StringUtilTest { @ParameterizedTest @CsvSource(value = {"6, 8, 0 :6,8,0", "1, 3, 4 :1,3,4"}, delimiter = ':') - void 공백_제거_테스트(String value, String expected) { + void removeBlankTest(String value, String expected) { assertThat(StringUtil.removeBlank(value)).isEqualTo(expected); } @ParameterizedTest @ValueSource(strings = {"6,8,10"}) - void shouldGetDataBit(String value) { + void parseTest(String value) { assertThat(StringUtil.parseByComma(value)).contains("6"); } + + @ParameterizedTest + @ValueSource (strings = {" ", "", " "}) + void checkBlankTest(String input){ + assertThatThrownBy(()->StringUtil.checkBlank(input)).isInstanceOf(EmptyInputException.class) + .hasMessageMatching("공백은 사용할 수 없습니다."); + } + + @Test + void checkNullTest(){ + assertThatThrownBy(()->StringUtil.checkNull(null)).isInstanceOf(NullPointerException.class) + .hasMessageMatching("널문자는 사용할 수 없습니다."); + } + + @ParameterizedTest + @ValueSource (strings = {"a", "으악", "34,"}) + void checkNumberFormatTest(String input){ + assertThatThrownBy(()->StringUtil.checkNumberFormat(input)).isInstanceOf(NumberFormatException.class) + .hasMessageMatching("문자열은 사용할 수 없습니다."); + } }