-
Notifications
You must be signed in to change notification settings - Fork 122
[완두콩] 조하은 로또 미션 제출합니다. #205
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
Merged
Merged
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
e37e586
feat: 로또 번호 생성 기능 구현
johaeunn 1aaeca7
feat: 구입 금액에 따라 로또를 생성하는 Lottos 구현
johaeunn 9fa4cfd
feat: 로또 번호 정렬 기능 추가
johaeunn 488dc15
feat: 로또 입출력 기능 구현
johaeunn ce2dd97
feat: 로또 애플리케이션 실행 흐름 구현
johaeunn 237b079
refactor: 구매 금액을 객체로 포장
johaeunn 0f17986
refactor: 로또 번호를 객체로 포장
johaeunn a64e5db
feat: 당첨 번호 검증 및 당첨 통계 기능 구현
johaeunn 2edb4e4
feat: 보너스 번호를 반영한 2등 당첨 기능 구현
johaeunn 6cd46af
refactor: 로또 당첨 등수와 상금 관리를 Enum으로 변경
johaeunn 3356338
feat: 로또 수동 구매 기능 구현
johaeunn bc267ed
refactor: 와일드카드 import를 명시적 import로 변경
johaeunn 3fda90c
refactor: 검증 로직을 별도 메서드로 분리
johaeunn 779b1e8
test: 전체 수동 구매 시 자동 구매 개수 테스트 추가
johaeunn e398223
test: LottoRank와 LottoStatistics 테스트 추가
johaeunn 43a4e1e
test: LottoNumber와 중복되는 Lotto 범위 테스트 제거
johaeunn 59618e0
docs: 로또 미션 README 작성
johaeunn 212d96c
test: 로또 수익률 계산 테스트 추가
johaeunn 3d834f6
fix: 구입 금액을 1000원 단위로 제한
johaeunn 8727403
refactor: LottoStatistics 생성 책임을 Application으로 이동
johaeunn 14aae3a
test: WinningNumbers 정상 생성 테스트 의도 명확화
johaeunn d973598
refactor: LottoRank에 MISS 상태 추가
johaeunn 4e1590b
refactor: 당첨 정보를 WinningLotto로 통합
johaeunn a100ceb
refactor: 로또 자동 생성 책임을 LottoGenerator로 분리
johaeunn a3c5304
test: 수동 로또의 구매 결과 포함 여부 검증
johaeunn ce39227
refactor: Application 실행 흐름을 의미 단위로 분리
johaeunn 8730e7c
refactor: 메서드 배치 순서를 호출 흐름에 맞게 정리
johaeunn f19affd
refactor: WinningNumbers를 Lotto로 통합
johaeunn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| # 로또 - 클린 코드 | ||
|
|
||
| ## 미션 소개 | ||
|
|
||
| 로또 구매 금액과 수동 구매 번호를 입력받아 로또를 발급하고, | ||
| 지난주 당첨 번호와 보너스 번호를 기준으로 당첨 결과와 수익률을 계산하는 프로그램 | ||
|
|
||
| --- | ||
|
|
||
| ## 요구사항 | ||
|
|
||
| - 로또 한 장의 가격은 1,000원이다. | ||
| - 로또 번호는 1부터 45 사이의 숫자이다. | ||
| - 하나의 로또는 서로 다른 6개의 번호로 구성된다. | ||
| - 구입 금액에 따라 구매 가능한 로또 개수를 계산한다. | ||
| - 사용자가 원하는 개수만큼 로또 번호를 수동으로 입력할 수 있다. | ||
| - 수동으로 구매하고 남은 개수는 자동으로 생성한다. | ||
| - 자동 로또 번호는 중복되지 않는 6개의 숫자로 생성한다. | ||
| - 지난주 당첨 번호 6개와 보너스 번호 1개를 입력받는다. | ||
| - 당첨 번호와 보너스 번호는 중복될 수 없다. | ||
| - 당첨 번호의 일치 개수와 보너스 번호 일치 여부에 따라 등수를 결정한다. | ||
| - 당첨 결과를 등수별로 집계한다. | ||
| - 총 당첨 금액을 기준으로 수익률을 계산한다. | ||
| - Java Enum을 활용하여 로또 등수와 상금을 관리한다. | ||
| - 메서드의 길이는 최대한 10라인을 넘지 않도록 구현한다. | ||
| - 들여쓰기 depth는 1을 넘지 않도록 구현한다. | ||
|
|
||
| ### 추가 구현 기준 | ||
| - 최소 구입 금액은 로또 1장 가격과 동일한 1,000원으로 정한다. | ||
| - 1,000원 미만의 금액을 입력하면 예외가 발생한다. | ||
|
|
||
| --- | ||
|
|
||
| ## 기능 목록 | ||
|
|
||
| ### 로또 구입 | ||
|
|
||
| - [x] 구입 금액 입력 | ||
| - [x] 최소 구입 금액 검증 | ||
| - [x] 구입 금액에 따른 총 구매 가능 개수 계산 | ||
| - [x] 수동 구매 개수 입력 | ||
| - [x] 수동 구매 개수가 음수인지 검증 | ||
| - [x] 수동 구매 개수가 전체 구매 가능 개수를 초과하는지 검증 | ||
| - [x] 수동 구매 개수를 제외한 자동 구매 개수 계산 | ||
|
|
||
| ### 로또 생성 | ||
|
|
||
| - [x] 수동 로또 번호 입력 | ||
| - [x] 수동 로또 번호 개수 검증 | ||
| - [x] 수동 로또 번호 중복 검증 | ||
| - [x] 자동 로또 번호 생성 | ||
| - [x] 로또 번호 범위 검증 | ||
| - [x] 로또 번호 정렬 | ||
| - [x] 수동 로또와 자동 로또를 하나의 컬렉션으로 관리 | ||
|
|
||
| ### 당첨 번호 | ||
|
|
||
| - [x] 지난주 당첨 번호 입력 | ||
| - [x] 당첨 번호 개수 검증 | ||
| - [x] 당첨 번호 중복 검증 | ||
| - [x] 보너스 번호 입력 | ||
| - [x] 보너스 번호와 당첨 번호의 중복 검증 | ||
|
|
||
| ### 당첨 결과 | ||
|
|
||
| - [x] 로또와 당첨 번호의 일치 개수 계산 | ||
| - [x] 보너스 번호 일치 여부 확인 | ||
| - [x] 일치 개수와 보너스 번호에 따른 당첨 등수 결정 | ||
| - [x] 등수별 당첨 개수 집계 | ||
| - [x] 총 당첨 금액 계산 | ||
| - [x] 수익률 계산 | ||
| - [x] 당첨 통계 출력 | ||
|
|
||
| --- | ||
|
|
||
| ## 당첨 기준 | ||
|
|
||
| | 등수 | 당첨 기준 | 상금 | | ||
| | --- | --- | ---: | | ||
| | 1등 | 6개 번호 일치 | 2,000,000,000원 | | ||
| | 2등 | 5개 번호 + 보너스 번호 일치 | 30,000,000원 | | ||
| | 3등 | 5개 번호 일치 | 1,500,000원 | | ||
| | 4등 | 4개 번호 일치 | 50,000원 | | ||
| | 5등 | 3개 번호 일치 | 5,000원 | | ||
|
|
||
| --- | ||
|
|
||
| ## 주요 객체 | ||
|
|
||
| | 객체 | 역할 | | ||
| | --- | --- | | ||
| | `PurchaseAmount` | 구입 금액을 관리하고 구매 가능한 로또 개수를 계산한다. | | ||
| | `LottoNumber` | 로또 번호 하나를 관리하고 번호의 범위를 검증한다. | | ||
| | `Lotto` | 6개의 로또 번호를 관리하고 자동 또는 수동으로 로또를 생성한다. | | ||
| | `Lottos` | 여러 개의 로또를 관리한다. | | ||
| | `WinningNumbers` | 당첨 번호를 관리하고 로또와 일치하는 번호 개수를 계산한다. | | ||
| | `LottoRank` | 당첨 등수와 상금을 관리한다. | | ||
| | `LottoStatistics` | 등수별 당첨 결과와 수익률을 계산한다. | | ||
| | `InputView` | 사용자 입력을 담당한다. | | ||
| | `OutputView` | 로또 및 당첨 결과 출력을 담당한다. | | ||
|
|
||
| --- | ||
|
|
||
| ## 테스트 | ||
|
|
||
| ### PurchaseAmountTest | ||
|
|
||
| - 구입 금액에 따른 로또 구매 개수 계산 | ||
| - 최소 구입 금액 검증 | ||
| - 수동 구매 개수가 음수인 경우 검증 | ||
| - 수동 구매 개수가 구매 가능 개수를 초과하는 경우 검증 | ||
| - 수동 구매 개수를 제외한 자동 구매 개수 계산 | ||
| - 모든 로또를 수동으로 구매한 경우 자동 구매 개수가 0인지 확인 | ||
|
|
||
| ### LottoNumberTest | ||
|
|
||
| - 로또 번호의 최소/최대 범위 확인 | ||
| - 로또 번호가 유효 범위를 벗어난 경우 예외 확인 | ||
|
|
||
| ### LottoTest | ||
|
|
||
| - 로또가 6개의 번호로 생성되는지 확인 | ||
| - 로또 번호 중복 여부 확인 | ||
| - 직접 지정한 번호가 6개가 아닌 경우 예외 확인 | ||
|
|
||
| ### LottosTest | ||
|
|
||
| - 수동 로또와 자동 로또를 합한 개수만큼 로또가 생성되는지 확인 | ||
| - 수동 구매가 없는 경우 자동 로또만 생성되는지 확인 | ||
|
|
||
| ### WinningNumbersTest | ||
|
|
||
| - 당첨 번호가 6개인지 확인 | ||
| - 당첨 번호 중복 검증 | ||
| - 보너스 번호와 당첨 번호 중복 검증 | ||
|
|
||
| ### LottoRankTest | ||
|
|
||
| - 일치 개수와 보너스 번호 일치 여부에 따라 올바른 당첨 등수를 반환하는지 확인 | ||
| - 당첨 기준에 미달하면 당첨되지 않는지 확인 | ||
|
|
||
| ### LottoStatisticsTest | ||
|
|
||
| - 실제 로또의 당첨 결과를 등수별로 올바르게 집계하는지 확인 | ||
|
|
||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import domain.LottoNumber; | ||
| import domain.LottoStatistics; | ||
| import domain.Lottos; | ||
| import domain.PurchaseAmount; | ||
| import domain.WinningLotto; | ||
| import domain.WinningNumbers; | ||
|
|
||
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class Application { | ||
| public static void main(String[] args) { | ||
| InputView inputView = new InputView(); | ||
| OutputView outputView = new OutputView(); | ||
|
|
||
| PurchaseAmount purchaseAmount = new PurchaseAmount(inputView.readPurchaseAmount()); | ||
|
|
||
| Lottos lottos = purchaseLottos(inputView, outputView, purchaseAmount); | ||
|
|
||
| WinningLotto winningLotto = createWinningLotto(inputView); | ||
|
|
||
| printStatistics(outputView, lottos, winningLotto, purchaseAmount); | ||
| } | ||
|
|
||
| private static Lottos purchaseLottos(InputView inputView, OutputView outputView, PurchaseAmount purchaseAmount) { | ||
| int manualLottoCount = inputView.readManualLottoCount(); | ||
| int autoLottoCount = purchaseAmount.calculateAutoLottoCount(manualLottoCount); | ||
|
|
||
| List<List<Integer>> manualLottoNumbers = inputView.readManualLottoNumbers(manualLottoCount); | ||
|
|
||
| Lottos lottos = new Lottos(manualLottoNumbers, autoLottoCount); | ||
|
|
||
| outputView.printPurchasedCount(manualLottoCount, autoLottoCount); | ||
| outputView.printLottos(lottos); | ||
|
|
||
| return lottos; | ||
| } | ||
|
|
||
| private static WinningLotto createWinningLotto(InputView inputView) { | ||
| WinningNumbers winningNumbers = new WinningNumbers(inputView.readWinningNumbers()); | ||
| LottoNumber bonusNumber = new LottoNumber(inputView.readBonusNumber()); | ||
|
|
||
| return new WinningLotto(winningNumbers, bonusNumber); | ||
| } | ||
|
|
||
| private static void printStatistics(OutputView outputView, Lottos lottos, | ||
| WinningLotto winningLotto, PurchaseAmount purchaseAmount) { | ||
| LottoStatistics lottoStatistics = new LottoStatistics(lottos, winningLotto); | ||
| outputView.printLottoStatistics(purchaseAmount, lottoStatistics); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
|
|
||
| public class Lotto { | ||
| private static final int NUMBER_COUNT = 6; | ||
|
|
||
| private final List<LottoNumber> numbers = new ArrayList<>(); | ||
|
|
||
| public Lotto(List<Integer> numberValues) { | ||
| validateNumberCount(numberValues); | ||
| validateDuplicateNumbers(numberValues); | ||
|
|
||
| convertToLottoNumbers(numberValues); | ||
|
|
||
| Collections.sort(numbers); | ||
| } | ||
|
|
||
| public List<LottoNumber> getNumbers() { | ||
| return List.copyOf(numbers); | ||
| } | ||
|
|
||
| private void convertToLottoNumbers(List<Integer> numberValues) { | ||
| for (int number : numberValues) { | ||
| LottoNumber lottoNumber = new LottoNumber(number); | ||
| numbers.add(lottoNumber); | ||
| } | ||
| } | ||
|
|
||
| private void validateNumberCount(List<Integer> numberValues) { | ||
| if (numberValues.size() != NUMBER_COUNT) { | ||
| throw new IllegalArgumentException("로또 번호는 " + NUMBER_COUNT + "개여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| private void validateDuplicateNumbers(List<Integer> numberValues) { | ||
| if (new HashSet<>(numberValues).size() != numberValues.size()) { | ||
| throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다."); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Random; | ||
|
|
||
| public class LottoGenerator { | ||
| private static final int NUMBER_COUNT = 6; | ||
| private static final int RANDOM_NUMBER_BOUND = 45; | ||
|
|
||
| private final Random random = new Random(); | ||
|
|
||
| public Lotto generate() { | ||
| return new Lotto(generateNumbers()); | ||
| } | ||
|
|
||
| private List<Integer> generateNumbers() { | ||
| List<Integer> numbers = new ArrayList<>(); | ||
|
|
||
| for (int i = 0; i < NUMBER_COUNT; i++) { | ||
| numbers.add(generateRandomNumber(numbers)); | ||
| } | ||
|
|
||
| return numbers; | ||
| } | ||
|
|
||
| private int generateRandomNumber(List<Integer> numbers) { | ||
| int number; | ||
|
|
||
| do { | ||
| number = random.nextInt(RANDOM_NUMBER_BOUND) + 1; | ||
| } while (containsNumber(numbers, number)); | ||
|
|
||
| return number; | ||
| } | ||
|
|
||
| private boolean containsNumber(List<Integer> numbers, int number) { | ||
| return numbers.contains(number); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package domain; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| public class LottoNumber implements Comparable<LottoNumber>{ | ||
| private static final int MIN_LOTTO_NUMBER = 1; | ||
| private static final int MAX_LOTTO_NUMBER = 45; | ||
|
|
||
| private final int number; | ||
|
|
||
| public LottoNumber(int number) { | ||
| validateNumberRange(number); | ||
|
|
||
| this.number = number; | ||
| } | ||
|
|
||
| public int getNumber() { | ||
| return number; | ||
| } | ||
|
|
||
| @Override | ||
| public int compareTo(LottoNumber lottoNumber) { | ||
| return Integer.compare(this.number, lottoNumber.number); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object object) { | ||
| if (this == object) { | ||
| return true; | ||
| } | ||
|
|
||
| if (!(object instanceof LottoNumber)) { | ||
| return false; | ||
| } | ||
|
|
||
| LottoNumber lottoNumber = (LottoNumber) object; | ||
|
|
||
| return this.number == lottoNumber.number; | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(number); | ||
| } | ||
|
|
||
| private void validateNumberRange(int number) { | ||
| if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) { | ||
| throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다."); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package domain; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
|
|
||
| public enum LottoRank { | ||
| MISS(0,0), | ||
| FIFTH(3, 5000), | ||
| FOURTH(4,50000), | ||
| THIRD(5, 1500000), | ||
| SECOND(5, 30000000), | ||
| FIRST(6, 2000000000); | ||
|
|
||
| private final int matchCount; | ||
| private final long prize; | ||
|
|
||
| LottoRank(int matchCount, long prize) { | ||
| this.matchCount = matchCount; | ||
| this.prize = prize; | ||
| } | ||
|
|
||
| public static LottoRank from(int matchCount, boolean bonusMatched) { | ||
| if (matchCount == 6) return FIRST; | ||
| if (matchCount == 5 && bonusMatched) return SECOND; | ||
| if (matchCount == 5) return THIRD; | ||
| if (matchCount == 4) return FOURTH; | ||
| if (matchCount == 3) return FIFTH; | ||
| return MISS; | ||
| } | ||
|
|
||
| public int getMatchCount() { | ||
| return matchCount; | ||
| } | ||
|
|
||
| public long getPrize() { | ||
| return prize; | ||
| } | ||
|
|
||
| public static List<LottoRank> winningRanks() { | ||
| return Arrays.stream(values()) | ||
| .filter(rank -> rank != MISS) | ||
| .toList(); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
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.
Application의 책임을 정리한 점이 좋습니다.여기서 한 단계 더 나아가고 싶다면, 로또 시스템의 실행 흐름을 담당하는 객체를 별도로 만들어보는 방법도 있습니다.
Application은 엄밀히 말하면 프로그램의 진입점이자 실행기 역할에 가깝기 때문에, 로또 구매부터 당첨 정보 생성과 통계 계산까지의 흐름을LottoGame,LottoController과 같은 객체에 위임할 수 있습니다.이렇게 하면 해당 실행기가 사용할
LottoGenerator도 외부에서 주입할 수 있어, 테스트에서 자동 생성 결과를 원하는 값으로 제어하기 쉬워집니다.현재 구조로도 전체 흐름은 충분히 읽히지만, 실행 흐름을 별도 객체에 위임하는 구조도 생각해볼 수 있습니다. 다만 현재 규모에서 객체를 추가하는 비용보다 얻는 이점이 큰지 함께 비교해보면 좋겠습니다.
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.
실행 흐름을 별도 객체로 분리하면
Application을 진입점 역할에 집중시킬 수 있고,LottoGenerator를 외부에서 주입하여 실행 흐름까지 테스트하기 쉬워진다는 장점이 있다는 것을 이해하였습니다.다만 현재는 로또 구매, 당첨 정보 생성, 통계 출력 정도로 실행 단계가 비교적 단순하고, 실제 계산이나 규칙은 각각의 도메인 객체가 담당하고 있어 Application은 주로 객체를 생성하고 연결하는 역할을 하고 있습니다. 이 상태에서 LottoGame이나 LottoController를 추가하면 현재로서는 기존 흐름을 한 번 더 감싸는 역할이 대부분일 것 같다는 생각이 들었습니다.
따라서 지금은 현재 구조를 유지하되, 이후 실행 흐름이 더 복잡해지거나
LottoGenerator를 주입하여 전체 흐름을 제어하는 테스트가 필요해진다면 별도의 실행 객체를 분리하는 방향을 고려해보겠습니다. 감사합니다!