Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,16 @@
### 결과출력
> ![1_결과출력](https://user-images.githubusercontent.com/29879110/155278106-3f5717ed-1885-43ba-a2ec-dcb4f10c6c4a.JPG)
<br>

## Mission3 - 수동구매 기능 추가
### 기능요구사항
- [x] 현재 로또 생성기는 자동 생성 기능만 제공한다. 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다.
- [x] 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다.
### 프로그래밍 요구사항
- [x] 예외가 발생하는 부분에 대해 자바 Exception을 적용해 예외처리한다.
- [x] 사용자가 입력한 값에 대한 예외 처리를 철저히 한다.
- [x] 상속과 인터페이스를 통해 구현을 간결히 할 수 없는지 고민해 본다.

### 결과출력
> ![1_결과출력](https://user-images.githubusercontent.com/29879110/155676112-fd7ccf8e-9a3e-48c1-9473-20863e490827.JPG)
<br>
76 changes: 27 additions & 49 deletions src/main/java/lotto/controller/LottoGame.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package lotto.controller;

import lotto.domain.LottoPaper;
import lotto.domain.LottoStore;
import lotto.domain.WinningStrategy;
import lotto.domain.*;
import lotto.view.InputView;
import lotto.view.OutputView;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class LottoGame {

Expand All @@ -19,74 +18,53 @@ public void start() {
InputView.init();

int purchaseAmount = InputView.getPurchaseAmount();
LottoPaper lottoPaper = purchase(purchaseAmount);
int manualLottoCount = InputView.getManualLottoCount();

List<WinningStrategy> winningStrategies = checkWinning(lottoPaper);
LottoPaper lottoPaper = purchase(
purchaseAmount,
manualLottoCount,
getManualNumbers(InputView.getManualLottoNumbers(manualLottoCount)));

calculateWinningStats(winningStrategies, purchaseAmount);
calculateWinningStats(checkWinning(lottoPaper), purchaseAmount);

InputView.close();
}

private LottoPaper purchase(int purchaseAmount) {
LottoStore lottoStore = new LottoStore();
LottoPaper lottoPaper = lottoStore.purchase(purchaseAmount);
private LottoPaper purchase(int purchaseAmount, int manualLottoCount, Map<Integer, List<Integer>> numbers) {
LottoPaper lottoPaper = new LottoStore().purchase(purchaseAmount, manualLottoCount, numbers);

Choose a reason for hiding this comment

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

LottoStore 는 구매요청이 들어올 때마다 새로 만들어져야만 하는 객체인지 궁금합니다.
도메인적인 요구 사항만 봐선 그렇지 않은 것 같은데요. LottoGame 이 인스턴스 변수로 관리해도 충분하다고 보여지는데 어떻게 생각하시는지요?

Copy link
Author

@ak2j38 ak2j38 Feb 27, 2022

Choose a reason for hiding this comment

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

맞습니다. 프로젝트 구현을 다시 되돌아보니 LottoStore는 하나만 존재하고 여러개의 LottoPaper를 발행할 수 있도록 구현했어야했습니다!
바로 수정하겠습니다 😄


OutputView.printPurchaseCount(lottoPaper.getLottoSize());
OutputView.printPurchaseCount(manualLottoCount, lottoPaper.getLottoSize() - manualLottoCount);
OutputView.printLottoPaper(lottoPaper.showLottoPaper());

return lottoPaper;
}

private List<WinningStrategy> checkWinning(LottoPaper lottoPaper) {
String inputWinningNumbers = InputView.getRequiredWinningNumber();
String inputBonusNumber = InputView.getBonusBallNumber();
List<Integer> winningNumbers = getNumbers(InputView.getRequiredWinningNumber());
int bonusNumber = getBonusNumber(InputView.getBonusBallNumber(winningNumbers));

List<Integer> winningNumbers = getWinningNumbers(inputWinningNumbers);
int bonusNumber = getBonusNumber(inputBonusNumber);

List<Integer> correctNumberCounts = lottoPaper.judgeWinning(winningNumbers);
List<Boolean> hasBonusNumbers = lottoPaper.hasBonusNumbers(bonusNumber);

return IntStream.range(0, correctNumberCounts.size())
.mapToObj(index -> convertMatchNumberToWinningStrategy(correctNumberCounts.get(index), hasBonusNumbers.get(index)))
.collect(Collectors.toList());
return lottoPaper.checkWinning(new WinningLotto(winningNumbers, bonusNumber));
}

private List<Integer> getWinningNumbers(String inputWinningNumbers) {
return Arrays.stream(inputWinningNumbers.split(NUMBER_DELIMITER))
private List<Integer> getNumbers(String inputNumbers) {
return Arrays.stream(inputNumbers.split(NUMBER_DELIMITER))
.mapToInt(Integer::parseInt)
.boxed()
.collect(Collectors.toList());
}

private int getBonusNumber(String inputBonusNumber) {
return Integer.parseInt(inputBonusNumber);
}
private Map<Integer, List<Integer>> getManualNumbers(Map<Integer, String> inputNumbers) {

Choose a reason for hiding this comment

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

List 자체가 순차성이 보장되는 컬렉션인데, 굳이 Map 의 키로 순서를 따로 보관할 이유가 있었는지 궁금해요.

Copy link
Author

Choose a reason for hiding this comment

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

Map 자료구조를 사용한 이유는 다음과 같습니다!

  1. 여러 개의 수동로또 번호를 입력받은 후(문자열 형태)
  2. LottoGame에서 문자열 리스트(Map의 value) 형태를 정수형 리스트(Map의 value) 형태로 변환하게 됩니다(LottoGame::getManualNumbers).
  3. 이 과정에서 각 수동 로또의 번호들은 따로 관리되어야 하는데 하나의 리스트로 합쳐지는 현상을 방지하기 위해 Map을 사용하였습니다.

<추가설명>
2의 과정에서 모든 수동로또들을 <순서, 수동로또번호> 형태로 반환해서 그렇습니다! Map을 사용하지 않는다면 하나의 List에 모든 수동로또번호가 계속 add가 되어 사용하는 쪽에서 6개마다 분리해주는 것보다 Map이 더 알맞다고 판단했습니다 😅

Map<Integer, List<Integer>> manualNumbers = new HashMap<>();

private WinningStrategy convertMatchNumberToWinningStrategy(int matchNumber, boolean hasBonusNumber) {
if (matchNumber == WinningStrategy.ZERO_MATCHES.getCorrectNumber()) {
return WinningStrategy.ZERO_MATCHES;
}
if (matchNumber == WinningStrategy.ONE_MATCHES.getCorrectNumber()) {
return WinningStrategy.ONE_MATCHES;
}
if (matchNumber == WinningStrategy.TWO_MATCHES.getCorrectNumber()) {
return WinningStrategy.TWO_MATCHES;
}
if (matchNumber == WinningStrategy.THREE_MATCHES.getCorrectNumber()) {
return WinningStrategy.THREE_MATCHES;
for (int i = 0; i < inputNumbers.size(); i++) {
manualNumbers.put(i, getNumbers(inputNumbers.get(i)));
}
if (matchNumber == WinningStrategy.FOUR_MATCHES.getCorrectNumber()) {
return WinningStrategy.FOUR_MATCHES;
}
if (matchNumber == WinningStrategy.FIVE_MATCHES.getCorrectNumber() && hasBonusNumber) {
return WinningStrategy.FIVE_WITH_BONUS_MATCHES;
}
if (matchNumber == WinningStrategy.FIVE_MATCHES.getCorrectNumber()) {
return WinningStrategy.FIVE_MATCHES;
}
return WinningStrategy.SIX_MATCHES;

return manualNumbers;
}

private int getBonusNumber(String inputBonusNumber) {
return Integer.parseInt(inputBonusNumber);
}

private void calculateWinningStats(List<WinningStrategy> winningStrategies, int purchaseAmount) {
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/lotto/domain/AutoLotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package lotto.domain;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class AutoLotto extends Lotto {
private static final int PICK_NUMBER_LENGTH = 6;

public AutoLotto(List<Integer> numbers) {
super(numbers);
}

@Override
public List<Integer> createLotto(List<Integer> numbers) {
return pickSixNumbers(numbers);
}
Comment on lines +14 to +17

Choose a reason for hiding this comment

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

지금 구현을 보면... 파라메터로는 항상 1부터 45까지의 숫자가 들어오는 건가요?

Copy link
Author

Choose a reason for hiding this comment

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

자동로또 생성 시에는 항상 1부터 45의 숫자가 들어오게 됩니다. 😃


private List<Integer> pickSixNumbers(List<Integer> numbers) {
Collections.shuffle(numbers);

return numbers.stream()
.limit(PICK_NUMBER_LENGTH)
.sorted()
.collect(Collectors.toList());
}
}
32 changes: 12 additions & 20 deletions src/main/java/lotto/domain/Lotto.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,23 @@
package lotto.domain;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class Lotto {
public abstract class Lotto {

Choose a reason for hiding this comment

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

아까 AutoLotto 의 구현을 보며 들었던 의문인데 파라메터로 항상 1부터 45까지의 숫자를 받을거라면...
추상 클래스의 이점을 살려서 여기서 1부터 45까지의 숫자가 초기화된 리스트를 관리하는 건 어떨까 의견 드려 봅니다.

Copy link
Author

@ak2j38 ak2j38 Feb 27, 2022

Choose a reason for hiding this comment

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

1부터 45의 숫자가 AutoLotto에서만 사용되고 있는데 AutoLotto가 아닌 Lotto에서 관리해아하는 이유가 혹시 따로 있을까요....??
제 생각은 Lotto를 상속받는 다른 클래스인 ManualLottoWinningLotto에서는 1~45가 사용되지 않을 것 같아 의견을 여쭙니다!


private final List<Integer> numbers;

public Lotto(List<Integer> numbers) {
this.numbers = pickSixNumbers(numbers);
this.numbers = createLotto(numbers);
}

private List<Integer> pickSixNumbers(List<Integer> numbers) {
int pickNumberLength = 6;

Collections.shuffle(numbers);

return numbers.stream()
.limit(pickNumberLength)
.sorted()
.collect(Collectors.toList());
}
public abstract List<Integer> createLotto(List<Integer> numbers);

public String showLottoNumbers() {
return numbers.toString();
}

public int getCorrectNumberCount(List<Integer> winningNumbers) {
return winningNumbers.stream()
.mapToInt(this::correctNumber)
.sum();
public boolean hasNumber(int number) {
return numbers.contains(number);
}

private int correctNumber(int number) {
Expand All @@ -39,7 +27,11 @@ private int correctNumber(int number) {
return 0;
}

public boolean hasBonusNumber(int bonusNumber) {
return numbers.contains(bonusNumber);
public MatchLottoResult match(WinningLotto winningLotto) {
int matchCount = (int)numbers.stream()
.filter(value -> winningLotto.hasNumber(value))
.count();

return new MatchLottoResult(matchCount, numbers.contains(winningLotto.getBonusNumber()));
}
}
14 changes: 5 additions & 9 deletions src/main/java/lotto/domain/LottoPaper.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,12 @@ public String showLottoPaper() {
return sb.toString();
}

public List<Integer> judgeWinning(List<Integer> winningNumbers) {
public List<WinningStrategy> checkWinning(WinningLotto winningLotto) {
return lottos.stream()
.mapToInt(lotto -> lotto.getCorrectNumberCount(winningNumbers))
.boxed()
.collect(Collectors.toList());
}

public List<Boolean> hasBonusNumbers(int bonusNumber) {
return lottos.stream()
.map(lotto -> lotto.hasBonusNumber(bonusNumber))
.map(lotto -> lotto.match(winningLotto))
.map(result -> WinningStrategy.convertMatchNumberToWinningStrategy(
result.getMatchNumberCount(),
result.hasBonus()))
.collect(Collectors.toList());
}
}
23 changes: 14 additions & 9 deletions src/main/java/lotto/domain/LottoStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

public class LottoStore {
private static final int LOTTO_PRICE = 1_000;
Expand All @@ -24,23 +25,27 @@ private List<Integer> getAllLottoNumber() {
return this.allLottoNumber;
}

private int getLottoCount(int purchaseAmount) {
return purchaseAmount / LOTTO_PRICE;
}

public LottoPaper purchase(int purchaseAmount) {
public LottoPaper purchase(int purchaseAmount, int manualLottoCount, Map<Integer, List<Integer>> numbers) {
int lottoCount = getLottoCount(purchaseAmount);

List<Lotto> lottos = createLottos(lottoCount);
List<Lotto> lottos = createLottos(manualLottoCount, lottoCount - manualLottoCount, numbers);

return new LottoPaper(lottos);
}

private List<Lotto> createLottos(int lottoCount) {
private int getLottoCount(int purchaseAmount) {
return purchaseAmount / LOTTO_PRICE;
}

private List<Lotto> createLottos(int manualLottoCount, int autoLottoCount, Map<Integer, List<Integer>> numbers) {
List<Lotto> lottos = new ArrayList<>();

for (int i = 0; i < lottoCount; i++) {
lottos.add(new Lotto(allLottoNumber));
for (int i = 0; i < manualLottoCount; i++) {
lottos.add(new ManualLotto(numbers.get(i)));
}

for (int i = 0; i < autoLottoCount; i++) {
lottos.add(new AutoLotto(allLottoNumber));
}

return lottos;
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/lotto/domain/ManualLotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package lotto.domain;

import java.util.List;
import java.util.stream.Collectors;

public class ManualLotto extends Lotto {

public ManualLotto(List<Integer> numbers) {
super(numbers);
}

@Override
public List<Integer> createLotto(List<Integer> numbers) {
return numbers.stream()
.sorted()
.collect(Collectors.toList());
}
}
19 changes: 19 additions & 0 deletions src/main/java/lotto/domain/MatchLottoResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package lotto.domain;

public class MatchLottoResult {

Choose a reason for hiding this comment

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

👍 👍 👍

private int matchNumberCount;
private boolean hasBonus;

public MatchLottoResult(int matchNumberCount, boolean hasBonus) {
this.matchNumberCount = matchNumberCount;
this.hasBonus = hasBonus;
}

public int getMatchNumberCount() {
return matchNumberCount;
}

public boolean hasBonus() {
return hasBonus;
}
}
25 changes: 25 additions & 0 deletions src/main/java/lotto/domain/WinningLotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package lotto.domain;

import java.util.List;
import java.util.stream.Collectors;

public class WinningLotto extends Lotto {

Choose a reason for hiding this comment

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

번호 일치 카운트를 알려주는 메소드도 있으면 좋겠네요!

Copy link
Author

Choose a reason for hiding this comment

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

수정 시 고려하겠습니다 😄


private final int bonusNumber;

public WinningLotto(List<Integer> numbers, int bonusNumber) {
super(numbers);
this.bonusNumber = bonusNumber;
}

@Override
public List<Integer> createLotto(List<Integer> numbers) {
return numbers.stream()
.sorted()
.collect(Collectors.toList());
}

public int getBonusNumber() {
return bonusNumber;
}
}
25 changes: 25 additions & 0 deletions src/main/java/lotto/domain/WinningStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,29 @@ public int getWinningPrice() {
public int getCorrectNumber() {
return this.correctNumber;
}

public static WinningStrategy convertMatchNumberToWinningStrategy(int matchNumber, boolean hasBonusNumber) {

Choose a reason for hiding this comment

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

헉 어떻게 좀 개선할 수 있는 방법이 없을까요? 전략 패턴을 사용하기에 좋은 케이스도 아닌듯 보입니다...

Choose a reason for hiding this comment

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

아 심지어 전략 패턴도 아니군요. 낚이기 좋은 enum 이름입니다...

Copy link
Author

Choose a reason for hiding this comment

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

그룹리뷰때도 한 분이 네이밍만 보고 전략패턴으로 오해하신 부분입니다 ㅠㅠㅠ
가이드 주신대로 네이밍도 변경해보고 로직도 개선해보겠습니다 😅

if (matchNumber == WinningStrategy.ZERO_MATCHES.getCorrectNumber()) {
return WinningStrategy.ZERO_MATCHES;
}
if (matchNumber == WinningStrategy.ONE_MATCHES.getCorrectNumber()) {
return WinningStrategy.ONE_MATCHES;
}
if (matchNumber == WinningStrategy.TWO_MATCHES.getCorrectNumber()) {
return WinningStrategy.TWO_MATCHES;
}
if (matchNumber == WinningStrategy.THREE_MATCHES.getCorrectNumber()) {
return WinningStrategy.THREE_MATCHES;
}
if (matchNumber == WinningStrategy.FOUR_MATCHES.getCorrectNumber()) {
return WinningStrategy.FOUR_MATCHES;
}
if (matchNumber == WinningStrategy.FIVE_MATCHES.getCorrectNumber() && hasBonusNumber) {
return WinningStrategy.FIVE_WITH_BONUS_MATCHES;
}
if (matchNumber == WinningStrategy.FIVE_MATCHES.getCorrectNumber()) {
return WinningStrategy.FIVE_MATCHES;
Comment on lines +31 to +50

Choose a reason for hiding this comment

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

스트림 API로 깔끔하게 개선할 수 있습니다. 시도해보시죠!

Copy link
Author

Choose a reason for hiding this comment

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

스트림으로 한번 개선해보겠습니다! 키워드 감사합니다 👍

}
return WinningStrategy.SIX_MATCHES;

Choose a reason for hiding this comment

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

만약에 matchNumber 로 8, 9, 10 이런 숫자들이 넘어온다면... 그럴 가능성이 아예 없으려나요?
공개된 API라서 그럴 수 있을 것 같다는 생각은 듭니다.

Copy link
Author

Choose a reason for hiding this comment

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

의도한 숫자들 이외의 값이 파라미터로 들어온다면 예외처리를 하는 로직을 추가해야겠군요!
메소드 자체를 공개된 API라고 생각해본 적이 없어서 예외처리가 미숙했던 것 같습니다. 감사합니다 👍

}
}
Loading