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 23 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
29 changes: 29 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Application과 동일 선상에 RunLotto 클래스 생성

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

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

Copy link

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.

우선 처음 목표는 입출력, 비즈니스 로직으로 나눠보고 싶었습니다.
각 파트에는 많은 메서드와 책임이 있습니다.
이때 책임을 분리하기위해 클래스를 나눴고, 관련 있는 클래스와 인터페이스를 묶는 것이 관련 기능을 찾거나 구현하기 쉬워지겠다 판단하여 패키지로 나눴습니다.
덕분에 가독성, 유지 보수성, 추후 공동 작업이나 재사용성의 이점이 생겼습니다.

Copy link

Choose a reason for hiding this comment

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

오.. .우선 매우 타당한 이유와 좋은 고민의 결과라는 생각이 듭니다.
그런데 저는 프로그래밍을 막 제대로 하기 시작했을 때, 가독성, 유지보수성 이라는 개념이 너무 막연했었거든요.
그래서 한 가지 감을 잡기 위한 힌트아닌 힌트?를 드리기 위해 한 가지 과제를 드려볼게요. (이거는 풀리퀘말고 개인톡으로 코드 캡쳐해서 보내주세요)
구매 수량 클래스의 생성자에 입력이 결합된 형태의 클래스를 만들어보았는데, 이 클래스가 똑바로 검증을 하는 지 어떻게 테스트 코드를 쓸 수 있을지 고민해보고 답 부탁드려요!

  public PurchaseAmount() {
    final var input = Console.readLine();
    validateIsNumeric(input);
    int inputAmount = Integer.parseInt(input);
    validatePurchaseAmount(amount);
    this.amount = inputAmount;
  }

동작 구조

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) {
RunLotto runLotto = new RunLotto();
runLotto.runLotto();
}
}
20 changes: 0 additions & 20 deletions src/main/java/lotto/Lotto.java

This file was deleted.

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

import java.util.List;
import java.util.Map;
import lotto.domain.Lotto;
import lotto.domain.PurchaseAmount;
import lotto.domain.UserInputNumbers;
import lotto.sevice.Calculator;
import lotto.util.InputManager;
import lotto.sevice.LottoGenerator;
import lotto.util.OutputManager;
import lotto.domain.WinningCheck;

public class RunLotto {
Copy link

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 InputManager inputManager = new InputManager();
private final LottoGenerator lottoGenerator = new LottoGenerator();
private final Calculator calculator = new Calculator();
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 = calculator.calculateWinningResult(generatedLottos,
userInputNumbers);
double profitRate = calculator.calculateProfitRate(purchaseAmount, result);

outputManager.printResults(result, profitRate);
}
Copy link

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.

헤헤 칭찬 감사합니다!

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

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

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("[ERROR] 로또 번호는 총 6개여야 합니다.");
}

for (int number : lottoNumbers) {
if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
}

Set<Integer> uniqueNumbers = new HashSet<>(lottoNumbers);
if (uniqueNumbers.size() < lottoNumbers.size()) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 중복이 없어야 합니다.");
}
}

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

public class PurchaseAmount {

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

public PurchaseAmount(String input) {
validateIsNumeric(input);
int inputAmount = Integer.parseInt(input);
validatePurchaseAmount(amount);
this.amount = inputAmount;
}

private static void validateIsNumeric(String inputAmount) {
Copy link

Choose a reason for hiding this comment

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

메서드를 static으로 설정해주신 이유가 있나요!?

Copy link
Author

@junseoplee junseoplee Jan 21, 2024

Choose a reason for hiding this comment

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

원래는 public static validate 메서드로 한번에 묶은 다음 클래스 외부의,입력 받는 메서드에 호출하여 사용하려 했습니다. 이후 책임 분리와 수정을 거쳤는데 이 부분은 수정을 하지 못했습니다. 지금 작성된 코드에서는 static일 필요가, 클래스 인스턴스를 생성하지 않고 바로 호출할 일이 없기에 수정하겠습니다.
추가적으로 domain 패키지 안에 있는 구입금액, 발행된 로또와 입력 받은 번호들은 한 번 초기화 이후 변경될 일이 없기에 불변으로 변경했습니다.

try {
Integer.parseInt(inputAmount);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] 구입금액은 숫자여야합니다.");
}
}
Copy link

Choose a reason for hiding this comment

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

domain 클래스가 String 이라는 input에 대해 관여하고 있는게 조금 걸립니다. 만약 입력이 콘솔이 아니라 웹 요청으로 오게 되어 항상 int 형이 오게 된다면 이 메서드는 사용하지 않는 메서드가 될 것 같아요

Copy link
Author

Choose a reason for hiding this comment

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

로컬 변수 amount를 불변으로 선언하고 싶었는데 해당 메서드(숫자인지 검사하는)로 인해 그렇게 하지 못하고 있었습니다. 생성자 안에서 int로 변경하는 과정을 거쳤기 때문이죠.
그래서 해당 과정과 NumberFormatException이 던져지는 경우를 입력 받는 메서드에서 처리하고 해당 클래스 자체를 리팩토링 했습니다.

Copy link

Choose a reason for hiding this comment

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

분리해주신 코드가 훨신 낫다고 느껴지네요!

수정 후 코드에선 InputManager 클래스(아마 프레젠테이션 계층)에서 Domain 계층의 값을 의존하고 있습니다!
이런 형태의 코드의 장점이 무엇일까요!?

(혹시 아직 프레젠테이션 레이어, Domain 레이어 라는 말이 익숙하지 않다면 이번 기회에 레이어드 아키텍쳐 라는 키워드로 학습하시고 블로그 글 하나 작성 부탁드립니다 !)

Copy link
Author

Choose a reason for hiding this comment

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

각 계층의 역할을 분명하게 할 수 있습니다.
우선 입력과 유효성 검사, 관리 역할로 확실하게 나뉘어 하나의 책임을 갖고 유지보수에 용이해집니다.
프레젠테이션 계층인 InputManager는 도메인 계층인 PurchaseAmount에 의존하고 있습니다.
상위 계층이 하위 계층에 의존하도록 하면, 코드의 변경에 대한 영향을 최소화할 수 있습니다.
자신보다 변하기 쉬운 것에는 의존해서는 안됩니다.
따라서 독립적인 테스트가 편리해지며 코드의 가독성도 향상됩니다.


private static void validatePurchaseAmount(int inputAmount) {
if (inputAmount % LOTTO_PRICE != 0) {
throw new IllegalArgumentException("[ERROR] 1000 단위의 금액을 입력하세요.");
}
}

public int getAmount() {
return amount;
}
}
57 changes: 57 additions & 0 deletions src/main/java/lotto/domain/UserInputNumbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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;

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 List<Integer> receivedLottoNumbers;
private 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("[ERROR] 로또 번호는 총 6개여야 합니다.");
}

for (int number : receivedLottoNumbers) {
if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
}

Set<Integer> uniqueNumbers = new HashSet<>(receivedLottoNumbers);
if (uniqueNumbers.size() < receivedLottoNumbers.size()) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 중복이 없어야 합니다.");
}
}

private void validateBonusNumbers(int bonusNumber) {
if (bonusNumber < MIN_LOTTO_NUMBER || bonusNumber > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}

if (new HashSet<>(receivedLottoNumbers).contains(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 중복이 없어야 합니다.");
}
}

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(5000, 3),
Copy link

Choose a reason for hiding this comment

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

소소한 팁: 자바에서 숫자가 너무 길어서 보기 힘들다면 5_000 처럼 쓸 수 있습니다.

Copy link
Author

Choose a reason for hiding this comment

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

오 신기하네요 좋습니다

FOURTH_PRIZE(50000, 4),
THIRD_PRIZE(1500000, 5, false),
SECOND_PRIZE(30000000, 5, true),
FIRST_PRIZE(2000000000, 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;
}
}
42 changes: 42 additions & 0 deletions src/main/java/lotto/sevice/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package lotto.sevice;
Copy link

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.

헉><


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

public class Calculator {
Copy link

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.

책임을 분리한다면 로또 결과 계산, 수익률의 계산 이렇게 두 책임으로 나눌 수 있습니다.
저라면 새로운 클래스를 만들고 싶습니다.
LottoResultCalculator와 LottoProfitRateCalculator로 클래스를 분리했고, 책임을 부여했습니다.
이제 각 클래스는 더 작게 나눠진 책임을 갖습니다. 향후 로직이 변경되거나 확장되는 경우, 유지보수에 이점이 있을 것입니다.

Copy link

Choose a reason for hiding this comment

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

x떡같이 말해도 찰떡같이 알아들으시는군요 👍
추가로 저는 Lotto클래스에 결과 계산의 책임을 주는 것도 괜찮아보여요! 만약 그렇다면 어떤식으로 파라미터를 받을 수 있을지 상상해보실래요!?


private static final int PERCENTAGE_FACTOR = 100;

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;
}

Copy link

Choose a reason for hiding this comment

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

이 책임을 Lotto 클래스나 WinningCheck enum 클래스에 넘겨주지 않고 별도의 Calculator 클래스를 만들게 된 이유가 궁금하네요 😎

Copy link
Author

Choose a reason for hiding this comment

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

단일 책임 원칙을 생각하며 별도의 클래스를 만들었습니다.
Lotto 클래스는 로또 객체를 유지하고 해당 로또가 사용자의 입력과 얼마나 일치하는지 알아내는 책임이 있고
WinningCheck enum 클래스는 일치하는 수와 보너스 일치 여부에 따라 어떤 상금이 주어지는지 결정하는 책임이 있습니다.
여기에 추가로 로또들의 목록을 순회하며 각각에 대한 결과를 계산하고 집계하는 이 메서드는 다른 책임입니다.
이 원칙에 따른다면 자신의 주요 책임에만 집중하게됩니다. 유지보수가 쉬워지며, 안정성이 높아집니다.

Copy link

Choose a reason for hiding this comment

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

아주 좋습니다! 추가적으로 세 클래스의 의존의 방향에 대해서도 이 기회에 한 번 가볍게 고민해보셔도 좋아보여요!!

public double calculateProfitRate(PurchaseAmount purchaseAmount,
Map<WinningCheck, Integer> result) {
double totalPrize = 0;
for (Map.Entry<WinningCheck, Integer> entry : result.entrySet()) {
WinningCheck prize = entry.getKey();
int count = entry.getValue();
totalPrize += prize.getPrizeAmount() * count;
}

Copy link

Choose a reason for hiding this comment

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

Suggested change
double totalPrize = 0;
for (Map.Entry<WinningCheck, Integer> entry : result.entrySet()) {
WinningCheck prize = entry.getKey();
int count = entry.getValue();
totalPrize += prize.getPrizeAmount() * count;
}
double totalPrize = (double) result.entrySet().stream()
.mapToInt(entry -> entry.getKey().getPrizeAmount() * entry.getValue())
.sum();

사실 어느게 깔끔한진 모르겠지만 이렇게 스트림으로 지역변수 선언과 할당을 동시에 할 수도 있습니다. ㅋㅋㅋ

Copy link
Author

Choose a reason for hiding this comment

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

저는 스트림이 더 좋은 코드라 생각합니다. 명확하네요!

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

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.getAmount() / 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;
}

}
Loading