-
Notifications
You must be signed in to change notification settings - Fork 72
[LBP] 윤준석 로또 미션 2-5단계 제출합니다. #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: junseok0304
Are you sure you want to change the base?
Changes from all commits
c87fff7
a701b7b
cbcbfb5
6f38163
0ab362a
e45c0d3
8346e52
08f759e
be25676
b40189e
b54e70f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package config; | ||
|
||
public enum LottoConstants { | ||
MIN_NUMBER(1), | ||
MAX_NUMBER(45), | ||
LOTTO_SIZE(6), | ||
LOTTO_PRICE(1000); | ||
|
||
private final int value; | ||
|
||
LottoConstants(int value) { | ||
this.value = value; | ||
} | ||
|
||
public int getValue() { | ||
return value; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package config; | ||
|
||
import java.util.*; | ||
|
||
public class LottoValidator { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 Validator 클래스가 유틸리티 성격을 띠고 있지만, Lotto와 LottoMachine에서도 자체적으로 검증을 하고 있어서 중복되는 코드가 많아 보여요 🤔 도메인에서 검증하는 방식과 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 예외메시지 출력을 위해서였지만 |
||
public static void validatePurchaseAmount(int amount) { | ||
if (amount <= 0) { | ||
throw new IllegalArgumentException("구매 금액은 0보다 커야 합니다."); | ||
} | ||
if (amount % 1000 != 0) { | ||
throw new IllegalArgumentException("구매 금액은 1000원 단위여야 합니다."); | ||
} | ||
} | ||
|
||
public static void validateManualTicketCount(int count) { | ||
if (count < 0) { | ||
throw new IllegalArgumentException("수동 구매 개수는 0 이상이어야 합니다."); | ||
} | ||
} | ||
|
||
public static List<Integer> validateAndParseLottoNumbers(String input) { | ||
List<Integer> numbers = Arrays.stream(input.split(",")) | ||
.map(String::trim) | ||
.map(Integer::parseInt) | ||
.toList(); | ||
Comment on lines
+22
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 메서드의 길이가 요구사항(10라인 제한)을 초과한 것 같아요. 검증 로직이 추가되면 더 길어질 가능성도 있어 보이네요 🤔 이 메서드에서는 입력을 나누는 역할과 검증하는 역할을 함께 하고 있는데, 혹시 입력을 나누는 로직이 다른 곳에서도 필요할 가능성이 있을까요? 만약 그렇다면 별도 메서드로 분리하면 어떤 장점이 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 입력값을 나누는 로직과 검증하는 로직을 분리하면 재사용성을 재고할 수 있고 |
||
|
||
if (numbers.size() != 6) { | ||
throw new IllegalArgumentException("로또 번호는 6개여야 합니다."); | ||
} | ||
|
||
Set<Integer> uniqueNumbers = new HashSet<>(numbers); | ||
if (uniqueNumbers.size() != 6) { | ||
throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다."); | ||
} | ||
|
||
if (numbers.stream().anyMatch(n -> n < 1 || n > 45)) { | ||
throw new IllegalArgumentException("로또 번호는 1~45 사이여야 합니다."); | ||
} | ||
|
||
return new ArrayList<>(uniqueNumbers); | ||
} | ||
|
||
public static void validateBonusNumber(int bonus) { | ||
if (bonus < 1 || bonus > 45) { | ||
throw new IllegalArgumentException("보너스 번호는 1~45 사이여야 합니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package config; | ||
|
||
import java.util.Arrays; | ||
import java.util.Optional; | ||
|
||
public enum WinningRank { | ||
MATCH_3(3, 5000, "3개 일치 (5000원)"), | ||
MATCH_4(4, 50000, "4개 일치 (50000원)"), | ||
MATCH_5(5, 1500000, "5개 일치 (1500000원)"), | ||
MATCH_5_BONUS(5, 30000000, "5개 일치, 보너스 볼 일치(30000000원)"), | ||
MATCH_6(6, 2000000000, "6개 일치 (2000000000원)"); | ||
Comment on lines
+7
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Java에서는 큰 숫자를 다룰 때 언더바를 활용할 수 있어요. 언더바를 사용하면 가독성이 좋아질 것 같은데, 어떻게 생각하시나요? MATCH_3(3, 5_000, "3개 일치 (5,000원)"),
MATCH_4(4, 50_000, "4개 일치 (50,000원)"),
... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오! 훨씬 나은 것 같습니다. 찾아보니 자바에서는 , 콤마가 예약어이므로 |
||
|
||
private final int matchCount; | ||
private final int prize; | ||
private final String description; | ||
|
||
WinningRank(int matchCount, int prize, String description) { | ||
this.matchCount = matchCount; | ||
this.prize = prize; | ||
this.description = description; | ||
} | ||
|
||
public int getPrize() { | ||
return prize; | ||
} | ||
|
||
public String getDescription() { | ||
return description; | ||
} | ||
|
||
public static Optional<WinningRank> findByMatchCount(int matchCount, boolean hasBonus) { | ||
if (matchCount == 5 && hasBonus) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이와 같이 하드 코딩된 조건문을 사용하는 것보다, Enum이 스스로 결정하고 검증할 수 있도록 하는 건 어떨까요? 이 방법에 대한 준석님의 의견이 궁금합니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Enum에서 스스로 상태를 결정해준다는 것에 대해서 생각해보지 못했는데, |
||
return Optional.of(MATCH_5_BONUS); | ||
} | ||
return Arrays.stream(values()) | ||
.filter(rank -> rank.matchCount == matchCount) | ||
.findFirst(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package controller; | ||
|
||
import model.Lotto; | ||
import model.LottoMachine; | ||
import model.LottoResultChecker; | ||
import config.LottoValidator; | ||
import view.InputView; | ||
import view.ResultView; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
|
||
public class LottoMachineController { | ||
public void run() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. run() 메서드가 다양한 책임을 가지고 있어요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 말씀해주신대로, 런에서 모두 처리하는 것 보다 메서드를 분리해서 호출해서 사용하는편이 더 나은 것 같습니다! |
||
int purchaseAmount; | ||
|
||
while (true) { | ||
try { | ||
purchaseAmount = InputView.getPurchaseAmount(); | ||
LottoValidator.validatePurchaseAmount(purchaseAmount); | ||
break; | ||
} catch (IllegalArgumentException e) { | ||
ResultView.printInvalidAmountMessage(); | ||
} | ||
} | ||
Comment on lines
+18
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 재입력을 받는 접근은 좋아요 👍🏻 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do while문 사용에 익숙치 않아서 위와 같은 방법으로 작성한 것 같습니다! |
||
|
||
int totalTicketCount = LottoMachine.getTicketCount(purchaseAmount); | ||
|
||
int manualTicketCount; | ||
while (true) { | ||
try { | ||
manualTicketCount = InputView.getManualTicketCount(); | ||
LottoValidator.validateManualTicketCount(manualTicketCount); | ||
if (manualTicketCount > totalTicketCount) { | ||
ResultView.printInvalidManualTicketMessage(); | ||
continue; | ||
} | ||
break; | ||
} catch (IllegalArgumentException e) { | ||
ResultView.printInvalidManualCountMessage(); | ||
} | ||
} | ||
|
||
int autoTicketCount = totalTicketCount - manualTicketCount; | ||
|
||
List<List<Integer>> manualNumbers; | ||
while (true) { | ||
try { | ||
List<String> inputNumbers = InputView.getManualLottoNumbers(manualTicketCount); | ||
manualNumbers = inputNumbers.stream() | ||
.map(LottoValidator::validateAndParseLottoNumbers) | ||
.collect(Collectors.toList()); | ||
break; | ||
} catch (IllegalArgumentException e) { | ||
ResultView.printInvalidLottoNumbersMessage(); | ||
} | ||
} | ||
|
||
List<Lotto> tickets = LottoMachine.generateLottos(manualNumbers, purchaseAmount); | ||
List<String> formattedTickets = tickets.stream().map(Lotto::toString).collect(Collectors.toList()); | ||
|
||
ResultView.printOrderTickets(manualTicketCount, autoTicketCount); | ||
ResultView.printTickets(formattedTickets); | ||
|
||
List<Integer> winningNumbers; | ||
while (true) { | ||
try { | ||
String input = InputView.getWinningNumbers(); | ||
winningNumbers = LottoValidator.validateAndParseLottoNumbers(input); | ||
break; | ||
} catch (IllegalArgumentException e) { | ||
ResultView.printInvalidWinningNumbersMessage(); | ||
} | ||
} | ||
|
||
int bonusNumber; | ||
while (true) { | ||
try { | ||
bonusNumber = InputView.getBonusNumber(); | ||
LottoValidator.validateBonusNumber(bonusNumber); | ||
break; | ||
} catch (IllegalArgumentException e) { | ||
ResultView.printInvalidBonusNumberMessage(); | ||
} | ||
} | ||
|
||
Map<String, Integer> results = LottoResultChecker.checkWinningResults(tickets, winningNumbers, bonusNumber); | ||
int totalPrize = results.get("totalPrize"); | ||
double profitRate = LottoResultChecker.calculateProfitRate(totalPrize, purchaseAmount); | ||
|
||
ResultView.printWinningStatistics(results, profitRate); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package model; | ||
|
||
import config.LottoConstants; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.stream.IntStream; | ||
|
||
public class LottoMachine { | ||
private static final int LOTTO_PRICE = LottoConstants.LOTTO_PRICE.getValue(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이미 이렇게 하면 상수의 계층이 하나 더 늘어나서 오히려 가독성이 떨어질 수도 있을 것 같아요. 준석님은 어떻게 생각하세요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그렇네요ㅜㅜ LottoConstants에서 값을 불러와서 사용하는 방식이 나을 것 같습니다! |
||
private static final List<Integer> LOTTO_NUMBER_POOL = | ||
IntStream.rangeClosed(LottoConstants.MIN_NUMBER.getValue(), LottoConstants.MAX_NUMBER.getValue()) | ||
.boxed() | ||
.toList(); | ||
|
||
public static int getTicketCount(int purchaseAmount) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. calculateTicketCount 라는 이름이 괜찮을 것 같습니다! |
||
if (purchaseAmount % LOTTO_PRICE != 0) { | ||
throw new IllegalArgumentException("구매 금액은 1000원 단위여야 합니다."); | ||
} | ||
return purchaseAmount / LOTTO_PRICE; | ||
} | ||
|
||
private static Lotto generateAutoLotto() { | ||
List<Integer> shuffledNumbers = new ArrayList<>(LOTTO_NUMBER_POOL); | ||
Collections.shuffle(shuffledNumbers); | ||
return new Lotto(shuffledNumbers.subList(0, LottoConstants.LOTTO_SIZE.getValue())); | ||
} | ||
|
||
public static List<Lotto> generateLottos(List<List<Integer>> manualNumbers, int purchaseAmount) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 메서드가 수동 로또 생성, 자동 로또 생성을 동시에 처리하고 있는 거 같아요. 각각 역할을 분리하면 가독성도 높아지고 한 가지 역할에 집중할 수 있을 것 같아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 말씀해주신대로, 수동 로또 생성과 자동 로또 생성을 분리해서 SRP를 준수하고 |
||
int totalTicketCount = getTicketCount(purchaseAmount); | ||
int manualCount = manualNumbers.size(); | ||
int autoCount = totalTicketCount - manualCount; | ||
|
||
if (manualCount > totalTicketCount) { | ||
throw new IllegalArgumentException("구매 가능한 로또 개수를 초과했습니다."); | ||
} | ||
|
||
List<Lotto> lottos = new ArrayList<>(); | ||
manualNumbers.forEach(numbers -> lottos.add(new Lotto(numbers))); | ||
for (int i = 0; i < autoCount; i++) { | ||
lottos.add(generateAutoLotto()); | ||
} | ||
|
||
return lottos; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package model; | ||
|
||
import config.WinningRank; | ||
import java.util.*; | ||
|
||
public class LottoResultChecker { | ||
|
||
public static Map<String, Integer> checkWinningResults(List<Lotto> purchasedLottos, List<Integer> winningNumbers, int bonusNumber) { | ||
Map<String, Integer> result = initializeResultMap(); | ||
int totalPrize = 0; | ||
Set<Integer> winningSet = new HashSet<>(winningNumbers); | ||
|
||
for (Lotto lotto : purchasedLottos) { | ||
totalPrize += updateResults(result, lotto, winningSet, bonusNumber); | ||
} | ||
|
||
result.put("totalPrize", totalPrize); | ||
return result; | ||
} | ||
|
||
private static Map<String, Integer> initializeResultMap() { | ||
Map<String, Integer> result = new LinkedHashMap<>(); | ||
for (WinningRank rank : WinningRank.values()) { | ||
result.put(rank.getDescription(), 0); | ||
} | ||
return result; | ||
} | ||
|
||
private static int updateResults(Map<String, Integer> result, Lotto lotto, Set<Integer> winningSet, int bonusNumber) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 메서드명은 당첨 결과를 업데이트하는 역할을 나타냈지만, 당첨 금액을 반환하는 역할도 하고 있어요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 메서드가 여러개의 역할을 하는 부분에 대해서 조금 더 주의깊게 생각해봐야할 것 같습니다, 감사합니다. |
||
int matchCount = (int) lotto.getNumbers().stream().filter(winningSet::contains).count(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. stream 사용 👍🏻 체이닝 방식을 쓰면 더 가독성 높아질 것 같아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스트림 체이닝에 대해서 공부해보겠습니다! |
||
boolean hasBonus = lotto.getNumbers().contains(bonusNumber); | ||
|
||
Optional<WinningRank> rank = WinningRank.findByMatchCount(matchCount, hasBonus); | ||
if (rank.isEmpty()) { | ||
return 0; | ||
} | ||
|
||
WinningRank winningRank = rank.get(); | ||
String key = winningRank.getDescription(); | ||
result.put(key, result.getOrDefault(key, 0) + 1); | ||
|
||
return winningRank.getPrize(); | ||
} | ||
|
||
public static double calculateProfitRate(int totalPrize, int purchaseAmount) { | ||
if (purchaseAmount == 0) { | ||
return 0.0; | ||
} | ||
return (double) totalPrize / purchaseAmount; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
package view; | ||
|
||
import java.util.List; | ||
|
||
public class ResultView { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enum 활용 👍🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
감사합니다!