Skip to content
26 changes: 26 additions & 0 deletions docs/step4요구사항.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 로또 구현

## 기능 요구사항

## 입력

- [x] 수동으로 구매할 로또 수를 입력받는다
- [x] 수동 구매 장수는 음수일수 없다.
- [x] 0개 입력시 전부 자동으로 구매한다.
- [x] 공백 입력 시 재입력받도록 한다.
- [x] 수동번호를 수동 갯수만큼 로또를 입력받는다.

## 수동 로또

- [x] 입력받은 로또 리스트를 받아서 생성한다.
- [x] 빈List이거나 null인경우 빈 값으로 판단한다.
- [x] 수동로또가 비어있으면 빈 리스트를 반환한다.

## 로또 티켓

- [x] 구입금액과 수동 로또를 받아서 생성한다.
- [x] 수동로또를 추가하고 남은 금만큼 자동 로또를 생성한다.

## 출력

- [x] 수동, 자동 장수를 출력한다.
12 changes: 10 additions & 2 deletions src/main/java/lotto/LottoApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@
import lotto.domain.Lotto;
import lotto.domain.LottoNumber;
import lotto.domain.LottoTickets;
import lotto.domain.LottoTicketsFactory;
import lotto.domain.ManualLottos;
import lotto.domain.PurchaseAmount;
import lotto.domain.WinningNumbers;
import lotto.domain.WinningResult;
import lotto.view.InputView;
import lotto.view.ResultView;

import java.util.List;

public class LottoApplication {
public static void main(String[] args) {
PurchaseAmount purchaseAmount = new PurchaseAmount(InputView.inputPurchaseAmount());
LottoTickets tickets = LottoTickets.create(purchaseAmount);

ResultView.printTicketCount(tickets.size());
int manualCount = InputView.inputManualLottoCount();
List<String> manualLottosInputs = InputView.inputManualLottos(manualCount);
ManualLottos manualLottos = new ManualLottos(manualLottosInputs);

LottoTickets tickets = LottoTicketsFactory.create(purchaseAmount, manualLottos);
ResultView.printTicketCount(tickets);
ResultView.printLottoTickets(tickets);

Lotto winningLotto = InputView.inputWinningNumbers();
Expand Down
15 changes: 2 additions & 13 deletions src/main/java/lotto/domain/Lotto.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,19 @@ public Lotto(Set<LottoNumber> numbers) {
}

private static Set<LottoNumber> numbersToSet(int[] intNumbers) {
validateInputSize(intNumbers.length);
Set<LottoNumber> numbers = new HashSet<>();
for (int number : intNumbers) {
numbers.add(LottoNumber.of(number));
}
validateNoDuplicate(numbers.size(), intNumbers.length);
return numbers;
}

public static Lotto from(List<Integer> intNumbers) {
return new Lotto(createLottoNumbers(intNumbers));
}

private static void validateNoDuplicate(int setSize, int inputSize) {
if (setSize != inputSize) {
throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다.");
}
}

private static void validateInputSize(int length) {
if (length != LOTTO_NUMBER_COUNT) {
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
throw new IllegalArgumentException("로또 번호는 중복없이 6개여야 합니다.");
}
}

Expand All @@ -54,8 +45,6 @@ private static Set<LottoNumber> createLottoNumbers(List<Integer> intNumbers) {
lottoNumbers.add(LottoNumber.of(number));
}

validateNoDuplicate(lottoNumbers.size(), intNumbers.size());
validateInputSize(lottoNumbers.size());
return lottoNumbers;
}

Expand All @@ -65,7 +54,7 @@ public List<Integer> getNumbers() {
result.add(number.getValue());
}
Collections.sort(result);
return result;
return List.copyOf(result);
}

public boolean contains(LottoNumber number) {
Expand Down
16 changes: 4 additions & 12 deletions src/main/java/lotto/domain/LottoNumber.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,16 @@ public class LottoNumber {

private final int value;

private LottoNumber(String value) {
this(Integer.parseInt(value));
}

private LottoNumber(int value) {
validate(value);
this.value = value;
}

public static LottoNumber of(int number) {
validate(number);
return CACHE.get(number);
}

private static void validate(int value) {
if (value < MIN_NUMBER || value > MAX_NUMBER) {
throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 한다");
LottoNumber lottoNumber = CACHE.get(number);
if (lottoNumber == null) {
throw new IllegalArgumentException(String.format("로또 번호는 %d부터 %d 사이의 숫자여야 한다", MIN_NUMBER, MAX_NUMBER));
}
return lottoNumber;
}

public int getValue() {
Expand Down
22 changes: 11 additions & 11 deletions src/main/java/lotto/domain/LottoTickets.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
package lotto.domain;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class LottoTickets {
Copy link
Contributor

Choose a reason for hiding this comment

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

이 객체가 담당하는 책임이 많아지고 있다.
Lotto 목록을 관리하면서 매칭 책임과 Lotto 목록을 생성하는 책임으로 두 가지 이상을 담당하고 있는 것 같다.
Lotto 목록을 생성하는 책임을 LottosFactory와 같은 다른 객체를 생성해 분리하면 어떨까?

'LottoTickets tickets = LottosFactory.createLottos(int purchaseAmount, List manualLottos)'

private final List<Lotto> lottos;
private final ManualLottos manualLottos;

public LottoTickets(List<Lotto> lottos) {
public LottoTickets(List<Lotto> lottos, ManualLottos manualLottos) {
this.lottos = lottos;
this.manualLottos = manualLottos;
}

public static LottoTickets create(PurchaseAmount purchaseAmount) {
int count = purchaseAmount.getLottoCount();
List<Lotto> lottos = new ArrayList<>();
for (int i = 0; i < count; i++) {
lottos.add(Lotto.from(LottoNumberGenerator.generate()));
}
return new LottoTickets(lottos);
public int getManualCount() {
return manualLottos.getCount();
}

public int getAutoCount() {
return lottos.size() - manualLottos.getCount();
}

public int size() {
return lottos.size();
}

public List<Lotto> getLottos() {
return new ArrayList<>(lottos);
return List.copyOf(lottos);
}

public WinningResult matchWith(WinningNumbers winningNumbers){
public WinningResult matchWith(WinningNumbers winningNumbers) {
Map<Rank, Integer> result = initializeResult();
for (Lotto lotto : lottos) {
Rank rank = winningNumbers.match(lotto);
Expand Down
59 changes: 59 additions & 0 deletions src/main/java/lotto/domain/LottoTicketsFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package lotto.domain;

import java.util.ArrayList;
import java.util.List;

public class LottoTicketsFactory {
Copy link
Contributor

Choose a reason for hiding this comment

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

이 객체의 복잡도가 높음
LottoTickets와 ManualLottos를 분리하지 말고 하나의 객체로 리팩터링할 방법은 없을까?
Lotto의 생성자에 String을 받아 List로 Lotto.from(String text)와 같은 정적 팩터리 메서드도 추가한다면...
로또 생성과 관련한 여러 측면에 고민을 추가로 해보면 어떨까?

public static LottoTickets create(PurchaseAmount purchaseAmount) {
int count = purchaseAmount.getLottoCount();
List<Lotto> lottos = generateAutoLottos(count);
return new LottoTickets(lottos, new ManualLottos(null));
}

public static LottoTickets create(PurchaseAmount purchaseAmount, ManualLottos manualLottos) {
validateManualCount(purchaseAmount, manualLottos);
// List<Lotto> allLottos = createLottos(purchaseAmount, manualLottos);
List<Lotto> lottos = convertToLottos(manualLottos);
List<Lotto> allLottos = createLottos(purchaseAmount, lottos, manualLottos.getCount());
return new LottoTickets(allLottos, manualLottos);
}

private static List<Lotto> convertToLottos(ManualLottos manualLottos) {
List<Lotto> lottos = new ArrayList<>();
for (String input : manualLottos.getManualLottosInput()) {
lottos.add(parseLotto(input));
}
return lottos;
}

private static Lotto parseLotto(String input) {
String[] tokens = input.split(",");
List<Integer> numbers = new ArrayList<>();
for (String token : tokens) {
numbers.add(Integer.parseInt(token.trim()));
}
return Lotto.from(numbers);
}


private static void validateManualCount(PurchaseAmount purchaseAmount, ManualLottos manualLottos) {
if (manualLottos.getCount() > purchaseAmount.getLottoCount()) {
throw new IllegalArgumentException("수동로또 개수가 구입 가능한 개수보다 많습니다.");
}
}

private static List<Lotto> createLottos(PurchaseAmount purchaseAmount, List<Lotto> manualLottosList, int manualCount) {
List<Lotto> lottos = new ArrayList<>(manualLottosList);
int autoCount = purchaseAmount.getLottoCount() - manualCount;
lottos.addAll(generateAutoLottos(autoCount));
return lottos;
}

private static List<Lotto> generateAutoLottos(int count) {
List<Lotto> lottos = new ArrayList<>();
for (int i = 0; i < count; i++) {
lottos.add(Lotto.from(LottoNumberGenerator.generate()));
}
return lottos;
}
}
22 changes: 22 additions & 0 deletions src/main/java/lotto/domain/ManualLottos.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package lotto.domain;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

public class ManualLottos {
private final List<String> manualLottosInput;

public ManualLottos(List<String> manualLottosInput) {
this.manualLottosInput = Optional.ofNullable(manualLottosInput)
.orElse(Collections.emptyList());
}

public int getCount() {
return manualLottosInput.size();
}

public List<String> getManualLottosInput() {
return List.copyOf(manualLottosInput);
}
}
58 changes: 56 additions & 2 deletions src/main/java/lotto/view/InputView.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ public class InputView {
private static final Scanner scanner = new Scanner(System.in);

public static int inputPurchaseAmount() {
System.out.println("구입금액을 입력해 주세요.");
return Integer.parseInt(scanner.nextLine());
while (true) {
try {
System.out.println("구입금액을 입력해 주세요.");
String input = scanner.nextLine();
validateNotEmpty(input);
return Integer.parseInt(input.trim());
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}

public static Lotto inputWinningNumbers() {
Expand All @@ -33,6 +41,52 @@ private static Lotto parseWinningNumbers(String input) {
public static LottoNumber inputBonusNumber() {
System.out.println("보너스 볼을 입력해 주세요.");
return LottoNumber.of(Integer.parseInt(scanner.nextLine()));
}

public static int inputManualLottoCount() {
while (true) {
try {
System.out.println("\n수동으로 구매할 로또 수를 입력해 주세요.");
String input = scanner.nextLine();
validateNotEmpty(input);
return Integer.parseInt(input.trim());
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}

public static List<String> inputManualLottos(int count) {
validateCount(count);
if (count == 0) {
return new ArrayList<>();
}

System.out.println("수동으로 구매할 번호를 입력해 주세요.");
List<String> manualLottos = new ArrayList<>();
for (int i = 0; i < count; i++) {
manualLottos.add(inputSingleManualLotto());
}
return manualLottos;
}

private static String inputSingleManualLotto() {
String input = scanner.nextLine();
validateNotEmpty(input);
//
// parseWinningNumbers(input);
return input.trim();
}

private static void validateCount(int count) {
if (count < 0) {
throw new IllegalArgumentException("음수를 입력할 수 없습니다.");
}
}

private static void validateNotEmpty(String input) {
if (input == null || input.trim().isEmpty()) {
throw new IllegalArgumentException("빈 값을 입력할 수 없습니다.");
}
}
}
6 changes: 4 additions & 2 deletions src/main/java/lotto/view/ResultView.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import java.util.List;

public class ResultView {
public static void printTicketCount(int count) {
System.out.println(String.format("%d개를 구매했습니다.", count));
public static void printTicketCount(LottoTickets tickets) {
int manualCount = tickets.getManualCount();
int autoCount = tickets.getAutoCount();
System.out.println(String.format("수동으로 %d장, 자동으로 %d개를 구매했습니다.", manualCount, autoCount));
}

public static void printLottoTickets(LottoTickets tickets) {
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/lotto/domain/LottoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ public class LottoTest {
void 로또_번호_6개가_아니면_예외발생() {
assertThatThrownBy(() -> new Lotto(1, 2, 3, 4, 5))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("로또 번호는 6개여야 합니다.");
.hasMessage("로또 번호는 중복없이 6개여야 합니다.");
}

@Test
void 로또_번호가_중복되면_예외발생() {
assertThatThrownBy(() -> new Lotto(1, 2, 3, 4, 5, 5))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("로또 번호는 중복될 수 없습니다.");
.hasMessage("로또 번호는 중복없이 6개여야 합니다.");
}

@Test
Expand Down
Loading