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

[2주차] 객체지향 코드연습 (Suehyun666) #31

Open
wants to merge 48 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e6ecb6a
lotto 클래스
Suehyun666 Sep 30, 2024
e287377
로또 진행 클래스
Suehyun666 Sep 30, 2024
41845b7
로또 생성 클래스
Suehyun666 Sep 30, 2024
69aba52
result
Suehyun666 Oct 5, 2024
928bcc2
Create OutputHandler.java
Suehyun666 Oct 5, 2024
87adba7
lotto generate
Suehyun666 Oct 5, 2024
26c32dd
lotto
Suehyun666 Oct 5, 2024
355ebf0
inputhandler
Suehyun666 Oct 5, 2024
619aae4
Update LottoGame.java
Suehyun666 Oct 5, 2024
7aaa590
inputhandler
Suehyun666 Oct 5, 2024
77bae68
1.application
Suehyun666 Oct 5, 2024
6049a3f
2. inputhandler
Suehyun666 Oct 5, 2024
a14bc17
3. 로또 클래스
Suehyun666 Oct 5, 2024
bff4f55
4. 결과계산
Suehyun666 Oct 5, 2024
0d78f68
5. 로또 생성기
Suehyun666 Oct 5, 2024
20eb9f3
6. 로또 게임
Suehyun666 Oct 5, 2024
97b2cd3
7. 출력 핸들러
Suehyun666 Oct 5, 2024
1a2ebfe
로또 클래스 다이어그램입니다.
Suehyun666 Oct 6, 2024
1858311
1.1 application
Suehyun666 Oct 9, 2024
fde267e
2.1 inputhandler
Suehyun666 Oct 9, 2024
f208822
2.2 inputconstants
Suehyun666 Oct 9, 2024
55be10e
2.3 inputvalidator
Suehyun666 Oct 9, 2024
a6c7390
3.1 로또 클래스
Suehyun666 Oct 9, 2024
a5440df
3.2 lottoconstants
Suehyun666 Oct 9, 2024
bcb6e36
3.3 로또 구매 정보
Suehyun666 Oct 9, 2024
e48a13c
4.1 결과계산
Suehyun666 Oct 9, 2024
3867749
4.2 결과계산 constants
Suehyun666 Oct 9, 2024
6de8f99
4.3 매치카운트 정보
Suehyun666 Oct 9, 2024
771e99b
7.1 출력 핸들러
Suehyun666 Oct 9, 2024
8382b15
6.1 로또게임
Suehyun666 Oct 9, 2024
64269f2
5.1 당첨번호 저장
Suehyun666 Oct 9, 2024
f018d51
Merge branch 'main' of https://github.com/Suehyun666/practice-oop-lotto
Suehyun666 Oct 9, 2024
89a61c1
삭제할 내용
Suehyun666 Oct 10, 2024
f7c2bbf
1.1.1application
Suehyun666 Oct 10, 2024
3430382
2.1 inputhandler
Suehyun666 Oct 10, 2024
743cfc9
2.2.1 InputConstants
Suehyun666 Oct 10, 2024
732acb0
2.3.1 InputValidator
Suehyun666 Oct 10, 2024
ec97241
3.1.1 lotto
Suehyun666 Oct 10, 2024
2aa1c6f
3.2.1 lottoconstants
Suehyun666 Oct 10, 2024
01e7fe1
3.3.1 purchaseinfo
Suehyun666 Oct 10, 2024
d2112f1
3.4.1 WinningInfo
Suehyun666 Oct 10, 2024
c1af815
4.1.1 resultcalculator
Suehyun666 Oct 10, 2024
96bbaf3
4.2.1 resultconstants
Suehyun666 Oct 10, 2024
4a63945
5.0.1 lottogenerator
Suehyun666 Oct 10, 2024
e764b62
7.1.1OutputHandler
Suehyun666 Oct 10, 2024
dfb20a1
7.2.1 outputconstants
Suehyun666 Oct 10, 2024
bea7598
6.1.1 lottogame
Suehyun666 Oct 10, 2024
958f0a5
4.3.1MatchResult
Suehyun666 Oct 10, 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
5 changes: 4 additions & 1 deletion src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package lotto;

public class Application {

public static void main(String[] args) {
// TODO: 프로그램 구현
LottoGame lottoGame = new LottoGame();
lottoGame.start();

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change

줄바꿈 신경써주세요.

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

import camp.nextstep.edu.missionutils.Console;

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

public class InputHandler {
Copy link

Choose a reason for hiding this comment

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

public 메서드가 private 메서드보다 위에 위치시켜주세요. 라고 제가 알고있었는 데 제가 다시 찾아보니 기능별로 응집하는 것도 좋다는 말도 있네요. 저도 개발하다보면 public 메서드들이 위에 많아져서 그 안에서 호출하는 private 메서드들이 많아져서 리팩토링할 때 조금 힘들었습니다.
하지만 다르게 보면 하나의 클래스의 역할이 너무 많아서 public private찾기가 힘들다고 볼 수 있겠네요. 이거에 대해서 고민해보세용. (private이 전부 다 위에 있는 건 장점이 없는 것 같아요.)
https://random-topic.tistory.com/159

private static final int WINNING_NUMBER_COUNT = 6;
private static final int BONUS_NUMBER_MIN = 1;
private static final int BONUS_NUMBER_MAX = 45;
private final OutputHandler outputHandler;

private List<Integer> parseWinningNumbers(String input) {
String[] tokens = input.split(",");
validateWinningNumbersCount(tokens);

List<Integer> numbers = new ArrayList<>();
for (String token : tokens) {
int number = Integer.parseInt(token.trim());
validateNumberRange(number);
numbers.add(number);
}

validateNoDuplicates(numbers);
return numbers;
}

private int parseBonusNumber(String input, List<Integer> winningNumbers) {
int bonusNumber = Integer.parseInt(input.trim());
validateBonusNumber(bonusNumber, winningNumbers);
return bonusNumber;
}

private void validateMoney(String input) {
if (!input.matches("\\d+") || Integer.parseInt(input) < LottoGame.LOTTO_PRICE) {
Copy link

Choose a reason for hiding this comment

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

노리신거라면 대단하신데 Integer.parseInt를 하기 전에 이렇게 숫자인 지 확인함으로써 NumberFormatException이 안발생하게 한거 좋은 것 같아요.
근데 Console.readLine();과 이 input.matches("\d+")를 합쳐서 readInt() 이런 메서드를 만드는 것도 좋아보여요. Console.readLine을 그대로 쓰는 것보다 그걸 가공한 메서드를 만들면 이 if문이 좀 짧아질 것 같아요. 제 의도가 전달됐나요?

throw new IllegalArgumentException("[ERROR] 유효한 금액을 입력해 주세요. 금액은 1000원 이상이어야 합니다.");
}
}

private void validateWinningNumbersCount(String[] tokens) {
Copy link

Choose a reason for hiding this comment

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

이 입력받는 클래스는 입력값에 대한 형태에 대한 예외처리를 처리하도록 하는 건 괜찮은 데 이제 비즈니스 적인 에외처리도 하고 있죠.
뭔가 분리하면 더 이 입력받는 객체의 역할이 줄어들지 않을까요?

if (tokens.length != WINNING_NUMBER_COUNT) {
throw new IllegalArgumentException("[ERROR] 당첨 번호는 6개여야 합니다.");
}
}

private void validateNumberRange(int number) {
if (number < BONUS_NUMBER_MIN || number > BONUS_NUMBER_MAX) {
throw new IllegalArgumentException("[ERROR] 번호는 1부터 45 사이여야 합니다.");
}
}

private void validateNoDuplicates(List<Integer> numbers) {
if (isDuplicated(numbers)) {
throw new IllegalArgumentException("[ERROR] 중복된 번호가 있습니다.");
}
}

private void validateBonusNumber(int bonusNumber, List<Integer> winningNumbers) {
if (bonusNumber < BONUS_NUMBER_MIN || bonusNumber > BONUS_NUMBER_MAX || winningNumbers.contains(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 1부터 45 사이의 당첨 번호와 중복되지 않는 번호여야 합니다.");
}
}

private boolean isDuplicated(List<Integer> numbers) {
Set<Integer> uniqueNumbers = new HashSet<>(numbers);
return uniqueNumbers.size() != numbers.size();
}

public InputHandler(OutputHandler outputHandler) {
this.outputHandler = outputHandler;
}

public int getMoney() {
outputHandler.printInputPrompt("구입 금액을 입력해 주세요.");
String input = Console.readLine();
validateMoney(input);
return Integer.parseInt(input);
}

public List<Integer> getWinningNumbers() {
outputHandler.printInputPrompt("당첨 번호를 입력해 주세요. (예: 1,2,3,4,5,6)");
String input = Console.readLine();
return parseWinningNumbers(input);
}

public int getBonusNumber(List<Integer> winningNumbers) {
outputHandler.printInputPrompt("보너스 번호를 입력해 주세요.");
String input = Console.readLine();
return parseBonusNumber(input, winningNumbers);
}


}
22 changes: 16 additions & 6 deletions src/main/java/lotto/Lotto.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
package lotto;

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

public class Lotto {

private final List<Integer> numbers;

private void validate(List<Integer> numbers) {
if (numbers.size() != 6 || isDuplicated(numbers)) {
throw new IllegalArgumentException("[ERROR] 잘못된 로또 번호입니다.");
}
}

private boolean isDuplicated(List<Integer> numbers) {
Set<Integer> uniqueNumbers = new HashSet<>(numbers);
return uniqueNumbers.size() != numbers.size();
}

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

private void validate(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException();
}
public List<Integer> getNumbers() {
return numbers;
}

// TODO: 추가 기능 구현
}
40 changes: 40 additions & 0 deletions src/main/java/lotto/LottoGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package lotto;

import java.util.List;

public class LottoGame {
Copy link
Contributor

Choose a reason for hiding this comment

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

이 클래스에 너무 많은 책임이 부여되어 있습니다.

  1. 입출력의 책임
  2. 로또를 생성하는 책임
  3. 입력한 String을 관리하기 쉽게 parsing해주는 역할
  4. 비즈니스 로직과 관련된 계산하는 로직
  5. 결과를 출력하기 위한 로직
  6. 입력한 값이 필요한 비즈니스 로직에 넘겨주는 책임

이외에도 여러 책임이 있고 조금 분리해야할 필요성이 보입니다.


private final InputHandler inputHandler;
private final LottoGenerator lottoGenerator;
private final ResultCalculator resultCalculator;
private final OutputHandler outputHandler;

public static final int LOTTO_PRICE = 1000;
Copy link
Contributor

Choose a reason for hiding this comment

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

접근제어자를 public으로 선언한 이유가 있을까요? 내부에서만 사용하는 것이 좋아보이고, 외부에서 사용하면 안된다고 생각합니다. 전체 클래스에서 공통적으로 사용하려면, 상수를 관리할 객체를 생성해주세요


public LottoGame() {
this.outputHandler = new OutputHandler();
this.inputHandler = new InputHandler(outputHandler);
this.lottoGenerator = new LottoGenerator();
this.resultCalculator = new ResultCalculator();
}
Copy link
Contributor

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.

LottoGame이 자신이 사용할 객체를 직접 고르면 LottoGame은 이 객체들의 변동사항에 대해 밀접하게 영향을 받게됩니다. 그러면 어떻게 해결해야할까요?


public void start() {
// 구입 금액 입력
int money = inputHandler.getMoney();
int lottoCount = money / LOTTO_PRICE;

// 로또 번호 자동 생성
List<Lotto> boughtLottos = lottoGenerator.generateLottos(lottoCount);

// 구매한 로또 출력
outputHandler.printLottos(boughtLottos);

// 당첨 번호와 보너스 번호 입력
List<Integer> winningNumbers = inputHandler.getWinningNumbers();
int bonusNumber = inputHandler.getBonusNumber(winningNumbers);

// 당첨 결과 계산 및 출력
int[] matchCounts = resultCalculator.calculateResults(boughtLottos, winningNumbers, bonusNumber);
outputHandler.printStatistics(matchCounts, lottoCount * LOTTO_PRICE);
Copy link

Choose a reason for hiding this comment

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

주석 처리는 최종 결과물에선 없애주세요. 없어도 읽히는 코드를 원합니다!

}
}
18 changes: 18 additions & 0 deletions src/main/java/lotto/LottoGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package lotto;
import camp.nextstep.edu.missionutils.Randoms;

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

public class LottoGenerator {
private static final int LOTTO_NUMBER_MIN = 1;
private static final int LOTTO_NUMBER_MAX = 45;
private static final int LOTTO_NUMBER_COUNT = 6;

public List<Lotto> generateLottos(int count) {
return IntStream.range(0, count)
.mapToObj(i -> new Lotto(Randoms.pickUniqueNumbersInRange(LOTTO_NUMBER_MIN, LOTTO_NUMBER_MAX, LOTTO_NUMBER_COUNT)))
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 내부에서 생성해도 괜찮을 것 같네요.
팩토리 메소드를 알아보시고 활용해보세요

.collect(Collectors.toList());
}
}
29 changes: 29 additions & 0 deletions src/main/java/lotto/OutputHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package lotto;

public class OutputHandler {
private static final int PRICE_THREE = 5000;
private static final int PRICE_FOUR = 50000;
private static final int PRICE_FIVE = 1500000;
private static final int PRICE_FIVE_BONUS = 30000000;
private static final int PRICE_SIX = 2000000000;
Copy link
Contributor

Choose a reason for hiding this comment

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

출력 객체에서 당첨 금액을 관리하고 있는 것은 적절하지 않은 것 같습니다. 당첨 금액을 관리할 객체를 추가해주세요


public void printLottos(java.util.List<Lotto> lottos) {
Copy link
Contributor

Choose a reason for hiding this comment

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

import가 안된 것 같습니다.

Suggested change
public void printLottos(java.util.List<Lotto> lottos) {
public void printLottos(List<Lotto> lottos) {

System.out.println(lottos.size() + "개를 구매했습니다.");
lottos.forEach(lotto -> System.out.println(lotto.getNumbers()));
}
public void printInputPrompt(String message) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
}
public void printInputPrompt(String message) {
}
public void printInputPrompt(String message) {

컨벤션 지켜주세요

System.out.println(message);
}

public void printStatistics(int[] matchCounts, int totalCost) {
System.out.println("3개 일치 (5,000원) - " + matchCounts[3] + "개");
System.out.println("4개 일치 (50,000원) - " + matchCounts[4] + "개");
System.out.println("5개 일치 (1,500,000원) - " + matchCounts[5] + "개");
System.out.println("5개 일치, 보너스 볼 일치 (30,000,000원) - " + matchCounts[7] + "개");
System.out.println("6개 일치 (2,000,000,000원) - " + matchCounts[6] + "개");
Copy link

Choose a reason for hiding this comment

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

위에 선제멘토님이 말한대로 matchCounts[] 라는 int[]을 사용하니까 앞으로의 유지보수가 좋아보이지 않아요.
그리고 enum이라는 열거형 타입 문법에 대해 알아보는 거 어떨까요. 5000원, 3개, 5등 뭔가 연관성 있어 보이지 않나요?
묶어서 관리해봅시다


double totalPrize = matchCounts[3] * PRICE_THREE + matchCounts[4] * PRICE_FOUR + matchCounts[5] * PRICE_FIVE + matchCounts[7] * PRICE_FIVE_BONUS + matchCounts[6] * PRICE_SIX;
double profitRate = (totalPrize / totalCost) * 100;
System.out.printf("총 수익률은 %.1f%%입니다.%n", profitRate);
}
}
44 changes: 44 additions & 0 deletions src/main/java/lotto/ResultCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package lotto;

import java.util.List;

public class ResultCalculator {
private static final int MATCH_THREE = 3;
private static final int MATCH_FOUR = 4;
private static final int MATCH_FIVE = 5;
private static final int MATCH_SIX = 6;
private static final int MATCH_FIVE_BONUS = 7; // 5개 + 보너스 일치

private void incrementCount(int[] matchCounts, int index) {
if (index >= 0) {
matchCounts[index]++;
}
}

private int getMatchCount(List<Integer> numbers, List<Integer> winningNumbers) {
return (int) numbers.stream().filter(winningNumbers::contains).count();
}

private int calculateResultIndex(int matchCount, boolean bonusMatch) {
Copy link
Contributor

Choose a reason for hiding this comment

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

private 메소드 전부 public 메소드 아래에 위치하도록 해주세요!

if (matchCount == MATCH_SIX) {return MATCH_SIX; // 6개 일치
} if (matchCount == MATCH_FIVE && bonusMatch) {return MATCH_FIVE_BONUS; // 5개 + 보너스 일치
Copy link
Contributor

Choose a reason for hiding this comment

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

가독성이 떨어지는 것 같습니다.

Suggested change
if (matchCount == MATCH_SIX) {return MATCH_SIX; // 6개 일치
} if (matchCount == MATCH_FIVE && bonusMatch) {return MATCH_FIVE_BONUS; // 5개 + 보너스 일치
if (matchCount == MATCH_SIX) {
return MATCH_SIX; // 6개 일치
}
if (matchCount == MATCH_FIVE && bonusMatch) {
return MATCH_FIVE_BONUS; // 5개 + 보너스 일치

} if (matchCount == MATCH_FIVE) {return MATCH_FIVE; // 5개 일치
} if (matchCount == MATCH_FOUR) {return MATCH_FOUR; // 4개 일치
} if (matchCount == MATCH_THREE) {return MATCH_THREE; // 3개 일치
}return -1; // 일치하지 않는 경우

}

public int[] calculateResults(List<Lotto> boughtLottos, List<Integer> winningNumbers, int bonusNumber) {
int[] matchCounts = new int[8];
Copy link

Choose a reason for hiding this comment

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

이 8이 무슨 의미인지 모르겠어요.
매직넘버 도입하면 알것같아요.


for (Lotto lotto : boughtLottos) {
int matchCount = getMatchCount(lotto.getNumbers(), winningNumbers);
boolean bonusMatch = lotto.getNumbers().contains(bonusNumber);

int resultIndex = calculateResultIndex(matchCount, bonusMatch);
incrementCount(matchCounts, resultIndex);
}
return matchCounts;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

matchCount를 int 로 관리하는 것보다, 객체를 생성하여 객체 내부의 상태로 관리하는 것이 좋습니다.
재사용성이나 유지보수성에서 더 좋습니다.

}