Skip to content
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

[로또] 이준섭 미션 제출합니다. #2112

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6cbdbef
feat: 로또 구입금액을 입력받은 후 입력 금액을 검증하는 기능 구현
junseoplee Jan 8, 2024
7276e90
junseoplee Jan 9, 2024
b6f34e0
Revert "경"
junseoplee Jan 9, 2024
5fb75e0
refactor: 패키지, 클래스 구분 및 생성, 구입금액 입력 및 유효성 검사 기능 재구현
junseoplee Jan 9, 2024
0bf4e6f
feat: 로또 번호를 생성 및 출력하는 기능 구현.
junseoplee Jan 10, 2024
93faf63
fix: 정렬이 제대로 되지 않는 문제를 해결, 변수명을 명확하게 변경
junseoplee Jan 10, 2024
14f83cd
fix: 실행결과를 양식에 맞게 수정
junseoplee Jan 10, 2024
c3ec26b
feat: 당첨 번호와 보너스 번호를 입력 받아 객체를 생성, 유효성 검사를 진행하는 기능 구현
junseoplee Jan 10, 2024
96f6737
fix: 불필요한 import 삭제
junseoplee Jan 11, 2024
0ccdc47
feat: 결과 출력 기능 구현
junseoplee Jan 11, 2024
e4cf662
feat: 구입금액이 숫자인지 검증하는 기능 구현
junseoplee Jan 12, 2024
c2c7bc9
feat: 수익률 계산 기능 구현
junseoplee Jan 12, 2024
68d01e7
docs: 구현할 기능 목록 정리
junseoplee Jan 13, 2024
cb5e447
docs: 구현할 기능 목록 정리
junseoplee Jan 15, 2024
177b560
refactor: 패키지와 클래스 구분 및 리팩토링, 보너스 번호 검사가 제대로 동작하지 않는 오류 수정
junseoplee Jan 15, 2024
c12be99
fix: getter의 위치 등 컨벤션 수정
junseoplee Jan 16, 2024
2b5fd6f
fix: 변수명 수정
junseoplee Jan 16, 2024
c0fda34
fix: 구현 수정
junseoplee Jan 17, 2024
1829f9c
docs: 구현할 기능 목록 정리
junseoplee Jan 17, 2024
32f7bd4
fix: WinningCheck enum의 상금을 String에서 int로 변경
junseoplee Jan 17, 2024
d62f6cd
fix: 불필요한 변수 및 주석 제거
junseoplee Jan 18, 2024
e22152e
docs: 해결한 문제 항목 삭제
junseoplee Jan 18, 2024
3e0d8fc
docs: 오타 수정
junseoplee Jan 18, 2024
76224f2
fix: 단위 구분을 위해 '_' 삽입
junseoplee Jan 21, 2024
58013f8
refactor: Calculator 클래스 책임 분리 및 수정
junseoplee Jan 21, 2024
c331b18
fix: 필요 없는 static 삭제와 안정성을 위한 final 추가
junseoplee Jan 21, 2024
ea0ea00
refactor: PurchaseAmount 클래스와 InputManager 리팩토링
junseoplee Jan 21, 2024
71be87d
fix: 클래스명을 명사형으로 변경
junseoplee Jan 21, 2024
9aa81c9
fix: 리팩토링된 항목들을 수정
junseoplee Jan 21, 2024
3ae6d9e
refactor: 에러 메세지를 enum에서 관리
junseoplee Jan 21, 2024
63bf89d
feat: 도메인 테스트 코드 구현
junseoplee Jan 24, 2024
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
30 changes: 30 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Application과 동일 선상에 RunLotto 클래스 생성

LottoManager에 필요한 기능들을 패키지로 묶어보자

- 도메인 모델 패키지 (domain)
- 사용자로부터 입력받은 구입금액을 저장하는 클래스 PurchaseAmount
- 난수 생성된 로또 번호가 있는 클래스 Lotto
- 사용자로부터 로또 번호 6개와 보너스 번호 1개를 입력받아 저장하는 클래스 UserInputNumbers
- 상금과 당첨 수를 저장하는 열거형 enum WinningCheck
- 비즈니스 로직을 수행하는 서비스 패키지 (service)
- 당첨 결과를 계산하는 클래스 LottoResultCalculator
- 수익률을 계산하는 클래스 LottoProfitRateCalculator
- 구입금액만큼 로또를 발행하는 클래스 LottoGenerator
- 각종 메서드가 있는 패키지 (util)
- 사용자로부터 값들을 입력받는 클래스 InputManager
- 결과들을 출력하는 클래스 OutputManager

동작 구조

1. receivePurchaseAmount()로 구입금액을 입력받아 PurchaseAmount에 객체로 저장(유효성 검사)
2. 생성된 PurchaseAmount를 받아 generateLottos(PurchaseAmount purchaseAmount)
입력받은 양 만큼 Lotto 객체로 로또를 발행(유효성 검사) -> 리턴 값은 List<Lotto>로 generatedLottos를 리턴
3. generatedLottos를 매개변수로 받아 printGeneratedLottoNumbers(List<Lotto> lottos) 생성된 로또들을 출력
4. calculateWinningResult(List<Lotto> lottos, UserInputNumbers receivedLotto)
Map<WinningCheck, Integer>을 이용해서 result를 생성 WinningCheck에 열거형으로 상금과 당첨 수를 저장
Lotto클래스에서 각 로또 당 matchCount와 bonusMatch를 가져온다 -> WinningCheck.getPrize(matchCount, bonusMatch)
-> 각 로또 당 상금과 수를 result에 저장 후 리턴
5. calculateProfitRate(PurchaseAmount purchaseAmount, Map<WinningCheck, Integer> result)
구입금액과 결과를 비교하여 수익률을 계산(소수 둘째에서 반올림) -> 리턴 값은 double profitRate
6. printResults(Map<WinningCheck, Integer> result, double profitRate) 최종 결과 출력
8 changes: 5 additions & 3 deletions src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package lotto;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
}

public static void main(String[] args) {
LottoManager lottoManager = new LottoManager();
lottoManager.runLotto();
}
}
20 changes: 0 additions & 20 deletions src/main/java/lotto/Lotto.java

This file was deleted.

38 changes: 38 additions & 0 deletions src/main/java/lotto/LottoManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package lotto;

import java.util.List;
import java.util.Map;
import lotto.domain.Lotto;
import lotto.domain.PurchaseAmount;
import lotto.domain.UserInputNumbers;
import lotto.service.LottoProfitRateCalculator;
import lotto.service.LottoResultCalculator;
import lotto.util.InputManager;
import lotto.service.LottoGenerator;
import lotto.util.OutputManager;
import lotto.domain.WinningCheck;

public class LottoManager {

private final InputManager inputManager = new InputManager();
private final LottoGenerator lottoGenerator = new LottoGenerator();
private final LottoResultCalculator lottoResultCalculator = new LottoResultCalculator();
private final LottoProfitRateCalculator lottoProfitRateCalculator = new LottoProfitRateCalculator();
private final OutputManager outputManager = new OutputManager();

public void runLotto() {
PurchaseAmount purchaseAmount = inputManager.receivePurchaseAmount();

List<Lotto> generatedLottos = lottoGenerator.generateLottos(purchaseAmount);
outputManager.printGeneratedLottoNumbers(generatedLottos);

UserInputNumbers userInputNumbers = inputManager.receiveLottoNumber();

Map<WinningCheck, Integer> result = lottoResultCalculator
.calculateWinningResult(generatedLottos, userInputNumbers);
double profitRate = lottoProfitRateCalculator
.calculateProfitRate(purchaseAmount, result);

outputManager.printResults(result, profitRate);
}
}
50 changes: 50 additions & 0 deletions src/main/java/lotto/domain/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package lotto.domain;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lotto.util.ErrorMessage;

public class Lotto {

public static final int MIN_LOTTO_NUMBER = 1;
public static final int MAX_LOTTO_NUMBER = 45;
public static final int SIZE_OF_LOTTO = 6;
private final List<Integer> lottoNumbers;

public Lotto(List<Integer> lottoNumbers) {
validateLottoNumbers(lottoNumbers);
this.lottoNumbers = lottoNumbers;
}

public int getMatchCount(UserInputNumbers receivedLotto) {
return (int) lottoNumbers.stream()
.filter(number -> receivedLotto.getReceivedLottoNumbers().contains(number))
.count();
}

public boolean isBonusMatch(UserInputNumbers receivedLotto) {
return lottoNumbers.contains(receivedLotto.getBonusNumber());
}

private void validateLottoNumbers(List<Integer> lottoNumbers) {
if (lottoNumbers.size() != SIZE_OF_LOTTO) {
throw new IllegalArgumentException(ErrorMessage.NOT_SIX_NUMBERS.getMessage());
}

for (int number : lottoNumbers) {
if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException(ErrorMessage.NOT_IN_RANGE.getMessage());
}
}

Set<Integer> uniqueNumbers = new HashSet<>(lottoNumbers);
if (uniqueNumbers.size() < lottoNumbers.size()) {
throw new IllegalArgumentException(ErrorMessage.MUST_NOT_DUPLICATE.getMessage());
}
}

public List<Integer> getLottoNumbers() {
return lottoNumbers;
}
}
24 changes: 24 additions & 0 deletions src/main/java/lotto/domain/PurchaseAmount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package lotto.domain;

import lotto.util.ErrorMessage;

public class PurchaseAmount {

public static final int LOTTO_PRICE = 1000;
private final int purchaseAmount;

public PurchaseAmount(int inputAmount) {
validatePurchaseAmount(inputAmount);
this.purchaseAmount = inputAmount;
}

private void validatePurchaseAmount(int inputAmount) {
if (inputAmount % LOTTO_PRICE != 0) {
throw new IllegalArgumentException(ErrorMessage.NOT_A_UNIT_OF_1000.getMessage());
}
}

public int getPurchaseAmount() {
return purchaseAmount;
}
}
58 changes: 58 additions & 0 deletions src/main/java/lotto/domain/UserInputNumbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package lotto.domain;

import static lotto.domain.Lotto.MIN_LOTTO_NUMBER;
import static lotto.domain.Lotto.MAX_LOTTO_NUMBER;
import static lotto.domain.Lotto.SIZE_OF_LOTTO;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lotto.util.ErrorMessage;

public class UserInputNumbers {
Copy link

Choose a reason for hiding this comment

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

이 코드는 Validation 만 수행하고 있는 클래스네요🤔 이 클래스의 책임으로 줄 수 있는 건 뭘까요!?

Copy link
Author

Choose a reason for hiding this comment

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

자신의 역할(유효성 검사, 저장, 제공)을 훌륭히 해내고 있는 클래스라고 생각합니다!
추가적인 책임을 주고싶지는 않습니다!

Copy link

Choose a reason for hiding this comment

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

좋습니다ㅋㅋㅋㅋ 사실 저도 요구사항 자체가 간단해서 책임 할당은 이 정도로 충분하다고 생각해요 🧐


private final List<Integer> receivedLottoNumbers;
private final int bonusNumber;

public UserInputNumbers(List<Integer> receivedLottoNumbers, int bonusNumber) {
validateLottoNumbers(receivedLottoNumbers);
this.receivedLottoNumbers = receivedLottoNumbers;
validateBonusNumbers(bonusNumber);
this.bonusNumber = bonusNumber;
}

private void validateLottoNumbers(List<Integer> receivedLottoNumbers) {
if (receivedLottoNumbers.size() != SIZE_OF_LOTTO) {
throw new IllegalArgumentException(ErrorMessage.NOT_SIX_NUMBERS.getMessage());
}

for (int number : receivedLottoNumbers) {
if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException(ErrorMessage.NOT_IN_RANGE.getMessage());
}
}

Set<Integer> uniqueNumbers = new HashSet<>(receivedLottoNumbers);
if (uniqueNumbers.size() < receivedLottoNumbers.size()) {
throw new IllegalArgumentException(ErrorMessage.MUST_NOT_DUPLICATE.getMessage());
}
}

private void validateBonusNumbers(int bonusNumber) {
if (bonusNumber < MIN_LOTTO_NUMBER || bonusNumber > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException(ErrorMessage.NOT_IN_RANGE.getMessage());
}

if (new HashSet<>(receivedLottoNumbers).contains(bonusNumber)) {
throw new IllegalArgumentException(ErrorMessage.MUST_NOT_DUPLICATE.getMessage());
}
}

public List<Integer> getReceivedLottoNumbers() { // 결과 계산에 호출됨
return receivedLottoNumbers;
}

public int getBonusNumber() {
return bonusNumber;
}
}
54 changes: 54 additions & 0 deletions src/main/java/lotto/domain/WinningCheck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package lotto.domain;

public enum WinningCheck {
LOSE(0, 0),
FIFTH_PRIZE(5_000, 3),
FOURTH_PRIZE(50_000, 4),
THIRD_PRIZE(1_500_000, 5, false),
SECOND_PRIZE(30_000_000, 5, true),
FIRST_PRIZE(2_000_000_000, 6);

private final int prizeAmount;
private final int matchingCount;
private final boolean hasBonus;

WinningCheck(int prizeAmount, int matchingCount) {
this.prizeAmount = prizeAmount;
this.matchingCount = matchingCount;
this.hasBonus = false;
}

WinningCheck(int prizeAmount, int matchingCount, boolean hasBonus) {
this.prizeAmount = prizeAmount;
this.matchingCount = matchingCount;
this.hasBonus = hasBonus;
}

public static WinningCheck getPrize(int matchCount, boolean bonusMatch) {
if (matchCount == 6) {
return FIRST_PRIZE;
}
if (matchCount == 5) {
return bonusMatch ? SECOND_PRIZE : THIRD_PRIZE;
}
if (matchCount == 4) {
return FOURTH_PRIZE;
}
if (matchCount == 3) {
return FIFTH_PRIZE;
}
return LOSE;
}

public int getPrizeAmount() {
return prizeAmount;
}

public int getMatchingCount() {
return matchingCount;
}

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

import static lotto.domain.Lotto.MAX_LOTTO_NUMBER;
import static lotto.domain.Lotto.MIN_LOTTO_NUMBER;
import static lotto.domain.Lotto.SIZE_OF_LOTTO;
import static lotto.domain.PurchaseAmount.LOTTO_PRICE;

import camp.nextstep.edu.missionutils.Randoms;
import java.util.ArrayList;
import java.util.List;
import lotto.domain.Lotto;
import lotto.domain.PurchaseAmount;

public class LottoGenerator {

public static List<Lotto> generateLottos(PurchaseAmount purchaseAmount) {
int numberOfLotto = purchaseAmount.getPurchaseAmount() / LOTTO_PRICE;
System.out.println("\n" + numberOfLotto + "개를 구매했습니다.");

List<Lotto> generatedLottos = new ArrayList<>();

for (int i = 0; i < numberOfLotto; i++) {
List<Integer> lottoNumbers =
Randoms.pickUniqueNumbersInRange(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER, SIZE_OF_LOTTO);

try {
Lotto lotto = new Lotto(lottoNumbers);
generatedLottos.add(lotto);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
i--;
}
}

return generatedLottos;
}

}
22 changes: 22 additions & 0 deletions src/main/java/lotto/service/LottoProfitRateCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package lotto.service;

import java.text.DecimalFormat;
import java.util.Map;
import lotto.domain.PurchaseAmount;
import lotto.domain.WinningCheck;

public class LottoProfitRateCalculator {

private static final int PERCENTAGE_FACTOR = 100;

public double calculateProfitRate(PurchaseAmount purchaseAmount,
Map<WinningCheck, Integer> result) {
double totalPrize = result.entrySet().stream()
.mapToInt(entry -> entry.getKey().getPrizeAmount() * entry.getValue())
.sum();

double profitRate = (totalPrize / purchaseAmount.getPurchaseAmount()) * PERCENTAGE_FACTOR;
DecimalFormat df = new DecimalFormat("#.##");
return Double.parseDouble(df.format(profitRate));
}
}
24 changes: 24 additions & 0 deletions src/main/java/lotto/service/LottoResultCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package lotto.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lotto.domain.Lotto;
import lotto.domain.UserInputNumbers;
import lotto.domain.WinningCheck;

public class LottoResultCalculator {

public Map<WinningCheck, Integer> calculateWinningResult(List<Lotto> lottos,
UserInputNumbers receivedLotto) {
Map<WinningCheck, Integer> result = new HashMap<>(); // WinningCheck 객체 정수를 키와 값으로 Map에 저장
for (Lotto lotto : lottos) {
int matchCount = lotto.getMatchCount(receivedLotto);
boolean bonusMatch = lotto.isBonusMatch(receivedLotto); // receivedLotto는 bonus를 포함하고있음

WinningCheck prize = WinningCheck.getPrize(matchCount, bonusMatch);
result.merge(prize, 1, Integer::sum);
}
return result;
}
}
Loading