-
Notifications
You must be signed in to change notification settings - Fork 122
[완두콩] 한지수 로또 미션 제출합니다. #204
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: luzknar
Are you sure you want to change the base?
Changes from all commits
eb33467
25a7a6d
bb9a74e
4bcc236
183109c
9f16875
e66d10f
6791807
f0bb06e
16aad70
1925288
1fd4040
48c0c1d
6be5649
e17cd36
464b224
76711b0
9c82f14
63180a1
a2e257b
88edef5
a30b33a
2bb1f54
cdd23cf
a741178
05a9d3d
2f4ed32
1775991
d145916
c626494
5b8dde5
0b6a05b
d5f0598
023460d
7e26f3a
efe62fe
bd23cfb
ce67ece
2e6e6f1
f8ec6f5
1d02d5d
69140af
c6082f7
5624c34
143eb9f
28a0430
b1a420a
63db64c
845e5f5
45501b2
cf6ab80
f891cea
afb6aad
048f3b1
b8e511f
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,49 @@ | ||
| # 로또 미션 | ||
|
|
||
| ---- | ||
|
|
||
| ### 기능 요구사항 | ||
|
|
||
| - 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야한다. | ||
| - 로또 1장의 가격은 1000원 이다. | ||
| - 로또 당첨 번호를 받아 일치한 번호 수에 따라 당첨 결과를 보여준다. | ||
| - 로또 2등을 위한 보너스볼을 추첨한다. | ||
| - 당첨 통계에 2등을 추가한다.(2등 당첨 조건은 당첨 번호 5개 일치 + 보너스 볼 일치다.) | ||
| - 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야한다. | ||
| - 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야한다. | ||
| --- | ||
|
|
||
| ### 프로그래밍 요구사항 | ||
|
|
||
| - 자바 코드 컨벤션을 지킨다. | ||
| - indent depth를 2를 넘지않도록 구현한다. | ||
| - 3항 연산자를 쓰지 않는다. | ||
| - else 예약어를 쓰지 않는다. | ||
| - 배열 대신 컬렉션을 사용한다. | ||
| - 축약 하지 않는다. | ||
| - 함수의 길이가 10라인을 넘지 않도록 구현한다. | ||
| - 모든 원시값과 문자열을 포장한다. | ||
| - 일급 컬렉션을 쓴다. | ||
| - Java Enum을 적용한다. | ||
|
|
||
| ### 예외 처리 케이스 | ||
| 사용자가 잘못된 값을 입력한 경우 IllegalArgumentException 또는 NumberFormatException 예외를 발생시키고, 에러 메시지 출력 후 재입력을 받는다. | ||
|
|
||
| #### 구입 금액 입력 예외 | ||
| - 숫자가 아닌 값을 입력한 경우 | ||
| - 0원 미만의 금액을 입력한 경우 ("구입 금액은 0원 이상이어야 합니다.") | ||
| - 1,000원 단위로 떨어지지 않는 금액을 입력한 경우 ("구입 금액은 1000원 단위이어야 합니다.") | ||
|
|
||
| ### 2. 수동 구매 수량 입력 예외 | ||
| - 숫자가 아닌 값을 입력한 경우 | ||
| - 수동 구매 수량이 0개 미만이거나 총 구입 가능 수량을 초과한 경우 ("수동 구매 수량은 0개 이상 N개 이하이어야 합니다.") | ||
|
|
||
| ### 3. 로또 번호 (수동 번호 및 당첨 번호) 검증 예외 | ||
| - 로또 번호가 6개가 아닌 경우 ("로또 번호는 6개여야 합니다.") | ||
| - 로또 번호가 1~45 범위를 벗어난 경우 ("로또 번호는 1부터 45까지여야 합니다.") | ||
| - 하나의 로또 내에 중복된 번호가 존재하는 경우 ("중복된 로또 번호가 존재합니다.") | ||
|
|
||
| ### 4. 보너스 볼 입력 예외 | ||
| - 숫자가 아닌 값을 입력한 경우 | ||
| - 보너스 볼 숫자가 1~45 범위를 벗어난 경우 ("보너스 볼은 1부터 45 사이의 숫자여야 합니다.") | ||
| - 당첨 번호 6개와 중복되는 숫자를 입력한 경우 ("중복된 로또 번호가 존재합니다.") |
|
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. 하나의 메서드에 쭉 로직이 작성되어있네요 🥲 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import domain.*; | ||
| import view.InputView; | ||
| import view.ResultView; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class Main { | ||
| public static void main(String[] args) { | ||
| final int price = InputView.getPurchaseAmount(); | ||
| final int manualCount = InputView.getManualPurchaseAmount(price); | ||
|
|
||
| PurchaseAmount purchaseAmount = new PurchaseAmount(price, manualCount); | ||
| PurchaseManage purchaseManage = new PurchaseManage(purchaseAmount); | ||
| List<String> manualInputs = InputView.getManualPurchasedLottos(manualCount); | ||
|
|
||
| Lottos lottos = purchaseManage.buyLottos(manualInputs); | ||
|
|
||
| ResultView.showNum(lottos); | ||
|
|
||
| String enteredWinningNumber = InputView.getWinningNumber(); | ||
| int bonusBall = InputView.getBonusNumber(); | ||
| WinningLotto winningLotto = new WinningLotto(enteredWinningNumber, bonusBall); | ||
| WinningStatistics winningStatistics = new WinningStatistics(); | ||
|
|
||
| winningStatistics.compareLottos(winningLotto, lottos); | ||
| ProfitRate profitRate = new ProfitRate(price, winningStatistics); | ||
| ResultView.showStatistics(profitRate, winningStatistics); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.HashSet; | ||
|
|
||
| public class Lotto { | ||
| private static final List<Integer> NUMBERS = new ArrayList<>(); | ||
| public static final int MAX_NUMBER = 45; | ||
| public static final int MIN_NUMBER = 1; | ||
| public static final int LOTTO_SIZE = 6; | ||
|
|
||
| static { | ||
| for (int i = 1; i <= MAX_NUMBER; i++) { | ||
| NUMBERS.add(i); | ||
| } | ||
| } | ||
|
|
||
| private final List<Integer> numbers; | ||
|
|
||
| public Lotto() { | ||
| List<Integer> numbers = new ArrayList<>(NUMBERS); | ||
| Collections.shuffle(numbers); | ||
| this.numbers = new ArrayList<>(numbers.subList(0, 6)); | ||
| Collections.sort(this.numbers); | ||
| } | ||
|
|
||
| public Lotto(List<Integer> manualNumbers) { | ||
| validateLottoNumber(manualNumbers); | ||
| validateDuplication(manualNumbers); | ||
| this.numbers = new ArrayList<>(manualNumbers); | ||
| Collections.sort(this.numbers); | ||
| } | ||
|
|
||
| private void validateLottoNumber(List<Integer> manualNumbers) { | ||
| for (int number : manualNumbers) { | ||
| validateNumber(number); | ||
| } | ||
| if (manualNumbers.size() != LOTTO_SIZE) { | ||
| throw new IllegalArgumentException("로또 번호는 6개여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| private void validateNumber(int number) { | ||
| if (number < MIN_NUMBER || number > MAX_NUMBER) { | ||
| throw new IllegalArgumentException("로또 번호는 1부터 45까지여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| private void validateDuplication(List<Integer> manualNumbers) { | ||
| Set<Integer> uniqueNumbers = new HashSet<>(manualNumbers); | ||
| if (uniqueNumbers.size() != manualNumbers.size()) { | ||
| throw new IllegalArgumentException("중복된 로또 번호가 존재합니다."); | ||
| } | ||
| } | ||
|
|
||
| public List<Integer> getLottoNumbers() { | ||
| return List.copyOf(numbers); | ||
| } | ||
|
Comment on lines
+59
to
+61
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. getter로 lottoNumbers를 그대로 넘겨주면 어떤 문제가 발생할 수 있을까요? 추가로 방어적 복사 에 대해 들어보셨나요?
Author
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. final 키워드는 lottoNumbers 변수의 재할당만 막아줄 뿐, 참조하는 내부 요소를 변경하는 것은 막지 못합니다. 따라서 외부에서 getLottoNumbers를 호출한뒤 .add(), .remove(), clear() 같은 메서드를 호출해서 수정이 가능하다는 문제가 있습니다. LottoNumber 객체의 내부 검증을 완벽히 통과해서 생성되었더라도, 외부에서 상태가 오염되는 위험이 생깁니다. 방어적 복사란 내부의 객체를 반환 할 때, 객체의 복사본을 만들어서 반환하는 것입니다.
new ArrayList<>() 로 복사한 리스트는 변경이 가능하기 때문에, List.copyOf()를 적용하였습니다. 방어적 복사를 통해 외부와의 참조를 끊어내는 동시에, 반환된 리스트 역시 변경 불가능한 상태로 만들어 객체의 불변성과 캡슐화를 확보하려 했습니다. 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 int get(int index) { | ||
| return numbers.get(index); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package domain; | ||
|
|
||
| import java.util.List; | ||
| import java.util.ArrayList; | ||
|
|
||
| public class LottoParser { | ||
|
|
||
| public static List<Integer> parseInput(String input) { | ||
| List<Integer> numbers = new ArrayList<>(); | ||
| for (String value : input.split(",")) { | ||
| numbers.add(Integer.parseInt(value.trim())); | ||
| } | ||
|
|
||
| return numbers; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| public class Lottos { | ||
| private List<Lotto> userLottos; | ||
|
|
||
| public Lottos() { | ||
| this.userLottos = new ArrayList<>(); | ||
| } | ||
| public Lottos(List<Lotto> lottos) { | ||
| this.userLottos = lottos; | ||
| } | ||
|
|
||
| public void makeManualLottos(List<String> manualInputs) { | ||
| for (String manualInput : manualInputs) { | ||
| Lotto manualLotto = new Lotto(LottoParser.parseInput(manualInput)); | ||
| this.userLottos.add(manualLotto); | ||
| } | ||
| } | ||
|
|
||
| public void makeAutomaticLottos(int automaticLottoCount) { | ||
| for (int i = 0; i < automaticLottoCount; i++) { | ||
| Lotto automaticLotto = new Lotto(); | ||
| this.userLottos.add(automaticLotto); | ||
| } | ||
| } | ||
|
|
||
| public int size() { | ||
| return userLottos.size(); | ||
| } | ||
| public List<Lotto> getLottos() { | ||
| return List.copyOf(userLottos); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package domain; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| public class ProfitRate { | ||
| private final int price; | ||
| private final WinningStatistics winningStatistics; | ||
|
|
||
| public ProfitRate(int price, WinningStatistics winningStatistics) { | ||
| this.price = price; | ||
| this.winningStatistics = winningStatistics; | ||
| } | ||
|
|
||
| public long getTotalProfit() { | ||
| long totalProfit = 0; | ||
| Map<Rank, Integer> statistics = winningStatistics.getWinningStatistics(); | ||
|
|
||
| for (Rank rank : statistics.keySet()) { | ||
| int count = statistics.get(rank); | ||
| totalProfit += (long) rank.getPrize() * count; | ||
| } | ||
| return totalProfit; | ||
| } | ||
|
|
||
| public double getProfitRate() { | ||
| return ((double) getTotalProfit() / price) * 100; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package domain; | ||
|
|
||
| public class PurchaseAmount { | ||
| public static final int LOTTO_PRICE = 1000; | ||
| private final int purchasePrice; | ||
| private final int totalCount; | ||
| private final int manualCount; | ||
|
|
||
| public PurchaseAmount(int price, int manualCount) { | ||
| this.purchasePrice = price; | ||
| totalCount = purchasePrice / LOTTO_PRICE; | ||
| validateManualCount(manualCount); | ||
| this.manualCount = manualCount; | ||
| } | ||
|
|
||
| private void validatePurchaseAmount(int purchasePrice) { | ||
| if (purchasePrice < 0) { | ||
| throw new IllegalArgumentException("구입 금액은 0원 이상이어야 합니다."); | ||
| } | ||
| if (purchasePrice % LOTTO_PRICE != 0) { | ||
| throw new IllegalArgumentException("구입 금액은 1000원 단위이어야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| private void validateManualCount(int manualCount) { | ||
| if (totalCount < manualCount || manualCount < 0) { | ||
| throw new IllegalArgumentException("수동 구매 수량은 0개 이상 " + totalCount + "개 이하이어야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| public int calculateAutomaticCount() { | ||
| return totalCount - manualCount; | ||
| } | ||
| } |
|
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. 해당 클래스의 역할에 대해서도 쭉 리스트업해보면 좋을 것같아요. 오히려 Lotto List를 필드로 가지고있는 Lottos가 가져야할 역할도 이 클래스가 가지고 있는 것 같은데 일단 리스트업 후 다른 여러 클래스들로 적절히 분리해보시죵 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package domain; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class PurchaseManage{ | ||
| private final PurchaseAmount purchaseAmount; | ||
|
|
||
| public PurchaseManage(PurchaseAmount purchaseAmount) { | ||
| this.purchaseAmount = purchaseAmount; | ||
| } | ||
| public Lottos buyLottos(List<String> manualInputs) { | ||
| int automaticLottoCount = purchaseAmount.calculateAutomaticCount(); | ||
| Lottos lottos = new Lottos(); | ||
| lottos.makeManualLottos(manualInputs); | ||
| lottos.makeAutomaticLottos(automaticLottoCount); | ||
|
|
||
| return lottos; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package domain; | ||
|
|
||
| public enum Rank { | ||
| FIRST_PLACE(6, 2000000000, false), | ||
| SECOND_PLACE_BONUS(5, 30000000, true), | ||
| SECOND_PLACE(5, 1500000, false), | ||
| THIRD_PLACE(4, 50000, false), | ||
| FOURTH_PLACE(3, 5000, false), | ||
| MISS(0, 0, false); | ||
|
|
||
| private final int matchBallNum; | ||
| private final int prize; | ||
| private final boolean hasBonusBall; | ||
|
|
||
| Rank(int matchBallNum, int prize, boolean hasBonusBall) { | ||
| this.matchBallNum = matchBallNum; | ||
| this.prize = prize; | ||
| this.hasBonusBall = hasBonusBall; | ||
| } | ||
|
|
||
| public static Rank getRank(int matchBallNum, boolean hasBonusBall) { | ||
| if (matchBallNum == FIRST_PLACE.getMatchBallNum()) { return FIRST_PLACE; } | ||
| if (matchBallNum == SECOND_PLACE_BONUS.getMatchBallNum() && hasBonusBall) { return SECOND_PLACE_BONUS; } | ||
| if (matchBallNum == SECOND_PLACE.getMatchBallNum() && !hasBonusBall) { return SECOND_PLACE; } | ||
| if (matchBallNum == THIRD_PLACE.getMatchBallNum()) { return THIRD_PLACE; } | ||
| if (matchBallNum == FOURTH_PLACE.getMatchBallNum()) { return FOURTH_PLACE; } | ||
| return MISS; | ||
| } | ||
|
|
||
| public int getMatchBallNum() { | ||
| return matchBallNum; | ||
| } | ||
|
|
||
| public int getPrize() { | ||
| return prize; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package domain; | ||
|
|
||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| public class WinningLotto { | ||
| private final Lotto winningLotto; | ||
| private final int bonusBall; | ||
| private int count = 0; | ||
| private boolean bonusFlag; | ||
|
|
||
| public WinningLotto(String enteredWinningLotto, int bonusBall) { | ||
| LottoParser lottoParser = new LottoParser(); | ||
| this.winningLotto = new Lotto(lottoParser.parseInput(enteredWinningLotto)); | ||
| validateBonusBall(bonusBall); | ||
| this.bonusBall = bonusBall; | ||
| } | ||
|
|
||
| private void validateBonusBall(int bonusBall) { | ||
| if (bonusBall < Lotto.MIN_NUMBER || bonusBall > Lotto.MAX_NUMBER) { | ||
| throw new IllegalArgumentException("보너스 볼은 1부터 45 사이의 숫자여야 합니다."); | ||
| } | ||
| List<Integer> copiedWinningNumber = winningLotto.getLottoNumbers(); | ||
| Set<Integer> uniqueBonusball = new HashSet<>(copiedWinningNumber); | ||
| if (!uniqueBonusball.add(bonusBall)) throw new IllegalArgumentException("중복된 로또 번호가 존재합니다."); | ||
| } | ||
|
|
||
| public int match(Lotto lotto) { | ||
| count = 0; | ||
| List<Integer> copiedLotto = lotto.getLottoNumbers(); | ||
| for (int i = 0; i < Lotto.LOTTO_SIZE; i ++) { | ||
| compareNumbers(copiedLotto, i); | ||
| } | ||
| if (count == 5) { | ||
| bonusFlag = hasBonusNumber(copiedLotto); | ||
| } | ||
| return count; | ||
| } | ||
| public void compareNumbers(List<Integer> copiedLotto, int i) { | ||
| if (copiedLotto.contains(winningLotto.get(i))) { | ||
| count++; | ||
| } | ||
| } | ||
|
|
||
| public boolean hasBonusNumber(List<Integer> copiedLotto) { | ||
| return copiedLotto.contains(bonusBall); | ||
| } | ||
|
|
||
| public boolean getBonusFlag() { | ||
| return bonusFlag; | ||
| } | ||
| } |
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.
예외 케이스 등이 구체화 되었는데 리드미를 업데이트해볼까요?