From d4ab190ae44434e4e79c30908034c1ec79dbcb30 Mon Sep 17 00:00:00 2001 From: hughryu1125 Date: Sat, 21 Feb 2026 10:43:19 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20lotto=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=ED=9D=90=EB=A6=84=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 113 +++++++++++++++++++ src/main/java/lotto/Application.java | 3 + src/main/java/lotto/Calculator.java | 25 ++++ src/main/java/lotto/Controller.java | 74 ++++++++++++ src/main/java/lotto/ErrorType.java | 14 +++ src/main/java/lotto/Input.java | 65 +++++++++++ src/main/java/lotto/Lotto.java | 45 +++++++- src/main/java/lotto/Output.java | 35 ++++++ src/main/java/lotto/RandomNumberCreator.java | 21 ++++ src/main/java/lotto/Rank.java | 27 +++++ src/main/java/lotto/Receipt.java | 21 ++++ src/main/java/lotto/Result.java | 11 ++ src/main/java/lotto/Service.java | 96 ++++++++++++++++ src/main/java/lotto/WinningLotto.java | 23 ++++ 14 files changed, 570 insertions(+), 3 deletions(-) create mode 100644 docs/README.md create mode 100644 src/main/java/lotto/Calculator.java create mode 100644 src/main/java/lotto/Controller.java create mode 100644 src/main/java/lotto/ErrorType.java create mode 100644 src/main/java/lotto/Input.java create mode 100644 src/main/java/lotto/Output.java create mode 100644 src/main/java/lotto/RandomNumberCreator.java create mode 100644 src/main/java/lotto/Rank.java create mode 100644 src/main/java/lotto/Receipt.java create mode 100644 src/main/java/lotto/Result.java create mode 100644 src/main/java/lotto/Service.java create mode 100644 src/main/java/lotto/WinningLotto.java diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..3d4c4146ec --- /dev/null +++ b/docs/README.md @@ -0,0 +1,113 @@ +## 설계 고민 +- 객체간의 종속성을 줄이는 방향 +- 객체의 기능과 책임을 어떻게 나눌 것인가.. + - 되도록이면 하나의 객체는 하나의 책임을! + - 정보를 많이 알고 있는 객체가 행위를! + - 직접 가져와서 쓰지 말고 요청을! + +validator를 따로 빼서 클래스로 갖고 있어야하나.. +그런데 그럼 검증은 정보를 가지고 있는 객체가 하기로 한 원칙을 위배해야 하는데.. + +* 참고 코드 = mjucraft -> recruit / 김영한 app config + +## 흐름 정리 +1. [Input]: 구입 금액 입력 -> [User] 전달 +2. [User]: 로또 개수 확정, +3. 각 로또의 번호 확정 +4. 당첨 번호 입력 +5. 보너스 번호 입력 +6. 당첨 여부 확정 +7. 수익률 계산 + +## 기능 정리 +1. 금액 -> 개수 +2. 랜덤 넘버 생성: random number creator +3. 당첨 넘버 생성 +4. [ 로또 넘버 <-> 당첨 넘버 체크 ] -> 각 당첨 등수별 개수 +5. 각 당첨 등수 개수 -> 금액 +6. 수익률 계산 +7. Error 발생 + +## 객체 정리 +### 1. Lotto + - 번호 6개 + - 번호 검증 함수(개수, 중복) + - 번호 일치 함수(순서 무관) + + +### 2. WinningLotto + - 번호 6개 + 보너스 1개 + - 번호 검증 함수(중복) + + +### 3. User + - 총 사용 금액 + - 로또들 + - 총 당첨 금액 + + +### 4. LottoBox + - 랜덤 넘버 생성 + - 위닝 로또 + +### 5. Input / Output + - 입력과 출력만 담당 + - 그 이상의 책임을 가지지 않는다. + +## 고민 포인트 +1. receipt는 로또 개수, 사용한 금액, 수익률, 상금등을 갖고만 있을건데 금액 -> 개수, 상금 + 사용한 금액 -> 수익률 이런 기능들은 누가 하고, 검증은 누가 해야 하지?? +2. Lotto +3. User 하위에 Receipt와 Lotteries를 둬서 자연스럽게 하고 싶었는데 그렇게 하는 의미가 없어졌어.. +4. 로또 번호들을 가질 Lotto가 번호를 검증해야 하는건 맞는데.. 중복 있으면 다시 요청해야 하잖아. 그걸 서비스에서 다 하는게 적절해 보이지 않아. +5. 즉, 현재의 문제 + - 로또 금액 검증을 책임질 객체가 없다. + - 원래 원칙은 가지고 있는 Receipt인데 직관적으로 봐도 너무 이상하다. + 얘는 그냥 값을 가져야 하는 애인데.. + - 그렇다고 service가 가지는 것도 이상해. + Calculator도 이상해. + - 검증을 정보를 가진 객체가 한다고 하자. 그럼 생성하는 애가 잘못된 애를 주면, 정보 주체는 검증 -> 다시 생성 주체 호출 -> 값 받기 이 과정을 반복해야 하는 것인가. +6. 결론: 생성 객체도 검증의 책임을 져야 한다. + - 근데 그럼 사용자가 입력하는 금액의 생성 객체는 Input인데 걔가 검증을 들고 있나? + +7. Service의 createLotteries는 금액 -> 개수 -> 로또 발급 + Receipt 작성까지 해야 하는데 + service의 구조상, 함수는 하나의 결과값을 return해줘야 하는데 List를 return 해주고 나면, + Receipt 작성은 어떻게 하는가? 함수를 또 따로 빼기엔 너무 구조가 겹치는데? + 뿐만 아니라 추후에 상금과 수익률을 넣어주는 것도 문제다. + 지금 구조에선, Receipt -> 상금, 수익률 계산 함수 -> 계산 -> 새로운 Receipt 기존의 정보 넣어서 생성 + -> 상금, 수익률 입력 -> 다시 return ; 미친 프로세스를 해야한다. + + +8. 해결 방법 + - User 만들자. 그래서 하위에 Receipt, Result, Lotteries를 다 가지고 있자. + 그래서 createLotteries에 User를 만들어 버리자. Result는 걍 초기화정도만 하고.. + - createLotteries를 더 쪼개는 방법. + - calculateAmountOfLotteriesWithMoney: 돈 -> 개수 + - createLotteriesWithAmount: 개수 -> 로또 생성 + - 최종 해결 방법: + - 우선 Receipt와 Result를 쪼개야 한다. + - createLotteries도 더 쪼개야 한다. createReceipt & createLotteries + - 그럼 이제 문제는 createLotteries를 할 때 Receipt을 줘서 할 것인가? + 아니면, Receipt의 개수(int, Receipt.getLotteriesAmount())만 떼어 줄 것인가? + 둘 다 문제가 있다. Receipt은 몰라도 되는 정보를 주고, 종속성에 문제가 생기고, + 개수를 주는 건, 나중에 컨트롤러에서 service의 함수에다가 Receipt.get~~를 써야한다. + - 결론: 아무리 컨트롤러에서 get하는게 적절해 보이지 않아도 Receipt를 넘기는 건 좀... + +9. 각 등수별 로또의 개수를 어떻게 저장하고 누가 정리할 것인가? + - 각 로또들이 등수를 계산해서 내는 건 말이 안돼. 얘가 너무 많은 책임을 가져. + 그럼 그냥 매칭 개수 정도만 return 해줄텐데.. 누군가는 이 정보들을 정리해야 돼. + service는 안돼. calculator가 하기에도 좀 그래.. + - 정보 정리는?? 그냥 리스트로 하기엔 각 인덱스가 몇등을 의미하는지 알기가 어렵다... + 물론 그건 규칙으로 만들 수 있지만 그리 직관적이지도 않고:: GPT한테 시키자. + +10. 해결 방법 + - 우선 Rank enum을 만들어서 enum으로 등수를 저장하자. + - 비교 -> Map 형태로 저장하자. + - Map -> Result 로 구현하자. 즉, 기존의 createResult를 두 단계로 쪼갰다. + - 문제점: service가 이걸 정리하는 과정이 매우 불편하다. 별로 적절해 보이지 않는다. + 그렇다고 lotteries 라는 객체를 만들어서 걔가 lotto를 리스트로 들고, + lotto 묶음의 결과를 가지고 다니는건 적절한가? + - 일단 지금 방식대로 하고 나중에 refactoring 해야 할 듯. + - 이런 설계가 나온 배경은 하나의 도메인에 너무 많은 객체들이 있다. 또 생성하는 것에 솔직히 부담감을 느낀다. + + +11. 문제: controller의 역할 = 도메인 외부와 내부의 연결 + 흐름 관리 \ No newline at end of file diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba4..63cae5efb8 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -3,5 +3,8 @@ public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 + Controller controller = new Controller(); + + controller.run(); } } diff --git a/src/main/java/lotto/Calculator.java b/src/main/java/lotto/Calculator.java new file mode 100644 index 0000000000..120b32ce18 --- /dev/null +++ b/src/main/java/lotto/Calculator.java @@ -0,0 +1,25 @@ +package lotto; + +import java.util.Map; + +public class Calculator { + + public int calculateAmountOfLotteriesWithMoney(int money) { + return money / 1000; + // magic number 처리 + } + + public Long calculatePrize(Map rankCount) { + Long prize = 0L; + + for (Rank rank : Rank.values()) { + prize += rankCount.get(rank).intValue() * rank.getPrize(); + } + + return prize; + } + + public double calculateProfitRate(int amountOfMoney, Long prize) { + return (double) amountOfMoney / prize; + } +} diff --git a/src/main/java/lotto/Controller.java b/src/main/java/lotto/Controller.java new file mode 100644 index 0000000000..a6805ee5a9 --- /dev/null +++ b/src/main/java/lotto/Controller.java @@ -0,0 +1,74 @@ +package lotto; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Controller { + // input / output과 service 연결 + private Input input = new Input(); + private Output output = new Output(); + private Service service = new Service(); + + public void run() { + Receipt receipt = createReceipt(); + int amountOfLotteries = receipt.getAmountOfLotteries(); + List lotteries = createLotteries(amountOfLotteries); + + WinningLotto winningLotto = createWinningLotto(); + + Map rankCounts = createRankCount(winningLotto, lotteries); + Result result = createResult(rankCounts, receipt); + + showProfitRate(result); + } + + private Receipt createReceipt() { + int purchaseAmount = input.readPurchaseAmount(); + Receipt receipt = service.createReceipt(purchaseAmount); + + int amountOfLotteries = receipt.getAmountOfLotteries(); + output.printPurchasedCount(amountOfLotteries); + + return receipt; + } + + private List createLotteries(int amountOfLotteries) { + List lotteries = service.createLotteries(amountOfLotteries); + List> lotteryNumbers = new ArrayList<>(); + + for (Lotto lotto : lotteries) { + lotteryNumbers.add(lotto.getNumbers()); + } + + output.printLotteries(lotteryNumbers); + + return lotteries; + } + + private WinningLotto createWinningLotto() { + List winningNumbers = input.readWinningNumbers(); + int bonusNumber = input.readBonusNumber(); + + return service.createWinningLotto(winningNumbers, bonusNumber); + } + + private Map createRankCount(WinningLotto winningLotto, List lotteries) { + //굳이 이긴 하다.. + Map rankCounts = service.createRankCounts(winningLotto, lotteries); + output.printWinningStatistics(rankCounts); + + return rankCounts; + } + + private Result createResult(Map rankCount,Receipt receipt) { + int amountOfMoney = receipt.getAmountOfMoney(); + return service.createResult(rankCount, amountOfMoney); + } + + private void showProfitRate(Result result) { + output.printProfitRate(result.profitRate); + } + + +} diff --git a/src/main/java/lotto/ErrorType.java b/src/main/java/lotto/ErrorType.java new file mode 100644 index 0000000000..150271d138 --- /dev/null +++ b/src/main/java/lotto/ErrorType.java @@ -0,0 +1,14 @@ +package lotto; + +public enum ErrorType { + + DUPLICATED_NUMBERS("[ERROR] 중복된 숫자는 허용되지 않습니다"), + LOTTO_COUNTS("[ERROR] 로또 번호는 6개여야 합니다."), + WRONG_AMOUNT_OF_MONEY("[ERROR] 금액은 1000원 단위여야 합니다."); + + private final String message; + + ErrorType(String message) { + this.message = message; + } +} diff --git a/src/main/java/lotto/Input.java b/src/main/java/lotto/Input.java new file mode 100644 index 0000000000..b18297187c --- /dev/null +++ b/src/main/java/lotto/Input.java @@ -0,0 +1,65 @@ +package lotto; + +import camp.nextstep.edu.missionutils.Console; + +import java.util.ArrayList; +import java.util.List; + +public class Input { + + public int readPurchaseAmount() { + System.out.println("구입금액을 입력해 주세요."); + String input = Console.readLine(); + + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("[ERROR] 구입 금액은 숫자여야 합니다."); + } + + if (!input.matches("\\d+")) { + throw new IllegalArgumentException("[ERROR] 구입 금액은 숫자여야 합니다."); + } + + return Integer.parseInt(input); + } + + public List readWinningNumbers() { + System.out.println("당첨 번호를 입력해 주세요."); + String input = Console.readLine(); + + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("[ERROR] 당첨 번호 입력이 올바르지 않습니다."); + } + + String[] tokens = input.split(","); + if (tokens.length != 6) { + throw new IllegalArgumentException("[ERROR] 당첨 번호는 6개여야 합니다."); + } + + List numbers = new ArrayList<>(); + for (String token : tokens) { + String value = token.trim(); + if (!value.matches("\\d+")) { + throw new IllegalArgumentException("[ERROR] 당첨 번호는 숫자여야 합니다."); + } + numbers.add(Integer.parseInt(value)); + } + + return numbers; + } + + public int readBonusNumber() { + System.out.println("보너스 번호를 입력해 주세요."); + String input = Console.readLine(); + + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("[ERROR] 보너스 번호는 숫자여야 합니다."); + } + + if (!input.matches("\\d+")) { + throw new IllegalArgumentException("[ERROR] 보너스 번호는 숫자여야 합니다."); + } + + return Integer.parseInt(input); + } + +} diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java index 88fc5cf12b..3745df4620 100644 --- a/src/main/java/lotto/Lotto.java +++ b/src/main/java/lotto/Lotto.java @@ -1,20 +1,59 @@ package lotto; +import java.util.ArrayList; import java.util.List; public class Lotto { private final List numbers; + // indent 2개 이하 지키기... + // TODO: Lotto랑 WinningLotto가 유사한 면이 있음. abstract class나 interface를 사용하는 것을 고민해봐야 함. + public Lotto(List numbers) { - validate(numbers); + validateCount(numbers); + validateDuplication(); this.numbers = numbers; } - private void validate(List numbers) { + public List getNumbers() { + return numbers; + } + + public int matchWinningNumbers(List winningNumbers) { + int count = 0; + + for (int number : numbers) { + if (winningNumbers.contains(number)) { + count++; + } + } + + return count; + } + + public boolean matchBonusNumber(int bonusNumber) { + for(int number : numbers){ + if(number == bonusNumber){ + return true; + } + } + + return false; + } + + private void validateCount(List numbers) { if (numbers.size() != 6) { throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다."); } } - // TODO: 추가 기능 구현 + private void validateDuplication() { + for (int i = 0; i < numbers.size() -1 ; i++) { + List restNumbers = numbers.subList(i + 1, numbers.size() -1); + if (restNumbers.contains(numbers.get(i))) { + throw new IllegalArgumentException("[ERROR] 겹치는 로또 번호가 있습니다."); + } + } + } + } diff --git a/src/main/java/lotto/Output.java b/src/main/java/lotto/Output.java new file mode 100644 index 0000000000..d87723968c --- /dev/null +++ b/src/main/java/lotto/Output.java @@ -0,0 +1,35 @@ +package lotto; + +import java.util.List; +import java.util.Map; + +public class Output { + + public void printPurchasedCount(int count) { + System.out.println(count + "개를 구매했습니다."); + } + + // 로또 번호는 이미 오름차순 정렬된 List라고 가정 + public void printLotteries(List> lotteries) { + for (List numbers : lotteries) { + System.out.println(numbers); + } + } + + public void printWinningStatistics(Map rankCounts) { + + System.out.println(); + System.out.println("당첨 통계"); + System.out.println("---"); + + System.out.println("3개 일치 (5,000원) - " + rankCounts.getOrDefault(Rank.Fifth, 0) + "개"); + System.out.println("4개 일치 (50,000원) - " + rankCounts.getOrDefault(Rank.Fourth, 0) + "개"); + System.out.println("5개 일치 (1,500,000원) - " + rankCounts.getOrDefault(Rank.Third, 0) + "개"); + System.out.println("5개 일치, 보너스 볼 일치 (30,000,000원) - " + rankCounts.getOrDefault(Rank.Second, 0) + "개"); + System.out.println("6개 일치 (2,000,000,000원) - " + rankCounts.getOrDefault(Rank.First, 0) + "개"); + } + + public void printProfitRate(double profitRatePercent) { + System.out.printf("총 수익률은 %.1f%%입니다.%n", profitRatePercent); + } +} diff --git a/src/main/java/lotto/RandomNumberCreator.java b/src/main/java/lotto/RandomNumberCreator.java new file mode 100644 index 0000000000..a714dac82a --- /dev/null +++ b/src/main/java/lotto/RandomNumberCreator.java @@ -0,0 +1,21 @@ +package lotto; + +import java.util.List; + +public class RandomNumberCreator { + public List createRandomLottoNumbers() { + List numbers = camp.nextstep.edu.missionutils.Randoms.pickUniqueNumbersInRange(1,45,6); + + return numbers; + } + + public int createUniqueRandomNumber(List excludedNumbers) { + int number = camp.nextstep.edu.missionutils.Randoms.pickNumberInRange(1, 45); + + while(!excludedNumbers.contains(number)){ + number = camp.nextstep.edu.missionutils.Randoms.pickNumberInRange(1, 45); + } + + return number; + } +} diff --git a/src/main/java/lotto/Rank.java b/src/main/java/lotto/Rank.java new file mode 100644 index 0000000000..57803f3a7d --- /dev/null +++ b/src/main/java/lotto/Rank.java @@ -0,0 +1,27 @@ +package lotto; + +public enum Rank { + First(6, false, 2000000000,"6개 일치 - 1등"), + Second(5, true, 30000000, "5개 일치 + 보너스 일치 - 2등"), + Third(5, false, 1500000, "5개 일치 + 보너스 불일치 - 3등"), + Fourth(4, false, 50000, "4개 일치 - 4등"), + Fifth(3,false, 5000, "3개 일치 - 5등"), + MISS(0, false, 0,""); // 굳이? + + + private final int matchCount; + private final boolean requiresBonus; + private final long prize; + private final String message; + + Rank(int matchCount, boolean requiresBonus, long prize, String message) { + this.matchCount = matchCount; + this.requiresBonus = requiresBonus; + this.prize = prize; + this.message = message; + } + + public long getPrize() { + return prize; + } +} diff --git a/src/main/java/lotto/Receipt.java b/src/main/java/lotto/Receipt.java new file mode 100644 index 0000000000..db0eba68bc --- /dev/null +++ b/src/main/java/lotto/Receipt.java @@ -0,0 +1,21 @@ +package lotto; + +import java.util.List; + +public class Receipt { + private int amountOfMoney; + private int amountOfLotteries; + + public Receipt(int amountOfMoney, int amountOfLotteries) { + this.amountOfMoney = amountOfMoney; + this.amountOfLotteries = amountOfLotteries; + } + + public int getAmountOfMoney() { + return amountOfMoney; + } + + public int getAmountOfLotteries() { + return amountOfLotteries; + } +} diff --git a/src/main/java/lotto/Result.java b/src/main/java/lotto/Result.java new file mode 100644 index 0000000000..f4dcb31840 --- /dev/null +++ b/src/main/java/lotto/Result.java @@ -0,0 +1,11 @@ +package lotto; + +public class Result { + Long amountOfPrize; + double profitRate; + + public Result(Long amountOfPrize, double profitability) { + this.amountOfPrize = amountOfPrize; + this.profitRate = profitability; + } +} diff --git a/src/main/java/lotto/Service.java b/src/main/java/lotto/Service.java new file mode 100644 index 0000000000..750d337d81 --- /dev/null +++ b/src/main/java/lotto/Service.java @@ -0,0 +1,96 @@ +package lotto; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Service { + Calculator calculator = new Calculator(); + RandomNumberCreator randomNumberCreator = new RandomNumberCreator(); + + public Receipt createReceipt(int amountOfMoney) { + + int amountOfLotteries = calculator.calculateAmountOfLotteriesWithMoney(amountOfMoney); + + Receipt receipt = new Receipt(amountOfMoney, amountOfLotteries); + + return receipt; + } + + public List createLotteries(int amountOfLotteries) { + // 개수 -> 로또 발급! + + List lotteries = new ArrayList<>(); + + for (int i = 0; i < amountOfLotteries; i++) { + List numbers = randomNumberCreator.createRandomLottoNumbers(); + Lotto lotto = new Lotto(numbers); + + lotteries.add(lotto); + } + // perhaps,, lotteries의 개수 검증..?? + return lotteries; + } + + public WinningLotto createWinningLotto(List numbers, int bonusNumber) { + WinningLotto winningLotto = new WinningLotto(numbers, bonusNumber); + + return winningLotto; + } + + public Map createRankCounts(WinningLotto winningLotto, List lotteries) { + Map rankCounts = new HashMap<>(); + + for (Rank rank : Rank.values()) { + rankCounts.put(rank, 0); + } + + List winningNumbers = winningLotto.getNumbers(); + int bonusNumber = winningLotto.getBonusNumber(); + + for (Lotto lotto : lotteries) { + int matchCount = lotto.matchWinningNumbers(winningNumbers); + boolean bonusMatch = lotto.matchBonusNumber(bonusNumber); + + Rank rank = determineRank(matchCount, bonusMatch); + rankCounts.put(rank, rankCounts.get(rank) + 1); + } + + return rankCounts; + } + + + public Result createResult(Map rankCount, int amountOfMoney){ + + Calculator calculator = new Calculator(); + Long prize = calculator.calculatePrize(rankCount); + + double profitRate = calculator.calculateProfitRate(amountOfMoney,prize); + + Result result = new Result(prize, profitRate); + + return result; + } + + private Rank determineRank(int matchCount, boolean bonusMatch) { + // 맘에 들지 않지만.. 얘를 누가 책임져야 할 지 더 고민해보자. + if (matchCount == 6) { + return Rank.First; + } + if (matchCount == 5 && bonusMatch) { + return Rank.Second; + } + if (matchCount == 5) { + return Rank.Third; + } + if (matchCount == 4) { + return Rank.Fourth; + } + if (matchCount == 3) { + return Rank.Fifth; + } + return Rank.MISS; + } + +} diff --git a/src/main/java/lotto/WinningLotto.java b/src/main/java/lotto/WinningLotto.java new file mode 100644 index 0000000000..e74c3d6354 --- /dev/null +++ b/src/main/java/lotto/WinningLotto.java @@ -0,0 +1,23 @@ +package lotto; + +import java.util.List; + +public class WinningLotto { + private List numbers; + private int bonusNumber; + + public WinningLotto(List numbers, int bonusNumber) { + // 최후의 보루로서 검증이 필요함. + + this.numbers = numbers; + this.bonusNumber = bonusNumber; + } + + public List getNumbers() { + return numbers; + } + + public int getBonusNumber() { + return bonusNumber; + } +} From 2770240da4ecf677c1c024c4a2979b4762b5dd05 Mon Sep 17 00:00:00 2001 From: hughryu1125 Date: Sat, 21 Feb 2026 10:52:59 +0900 Subject: [PATCH 2/5] =?UTF-8?q?chore:=20=EC=9D=98=EB=AF=B8=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F?= =?UTF-8?q?=20TODO=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 57 --------------------------- src/main/java/lotto/Application.java | 1 - src/main/java/lotto/Calculator.java | 2 +- src/main/java/lotto/ErrorType.java | 2 + src/main/java/lotto/Lotto.java | 1 - src/main/java/lotto/Output.java | 1 - src/main/java/lotto/Service.java | 2 - src/main/java/lotto/WinningLotto.java | 2 +- 8 files changed, 4 insertions(+), 64 deletions(-) diff --git a/docs/README.md b/docs/README.md index 3d4c4146ec..adaa352e70 100644 --- a/docs/README.md +++ b/docs/README.md @@ -54,60 +54,3 @@ validator를 따로 빼서 클래스로 갖고 있어야하나.. - 입력과 출력만 담당 - 그 이상의 책임을 가지지 않는다. -## 고민 포인트 -1. receipt는 로또 개수, 사용한 금액, 수익률, 상금등을 갖고만 있을건데 금액 -> 개수, 상금 + 사용한 금액 -> 수익률 이런 기능들은 누가 하고, 검증은 누가 해야 하지?? -2. Lotto -3. User 하위에 Receipt와 Lotteries를 둬서 자연스럽게 하고 싶었는데 그렇게 하는 의미가 없어졌어.. -4. 로또 번호들을 가질 Lotto가 번호를 검증해야 하는건 맞는데.. 중복 있으면 다시 요청해야 하잖아. 그걸 서비스에서 다 하는게 적절해 보이지 않아. -5. 즉, 현재의 문제 - - 로또 금액 검증을 책임질 객체가 없다. - - 원래 원칙은 가지고 있는 Receipt인데 직관적으로 봐도 너무 이상하다. - 얘는 그냥 값을 가져야 하는 애인데.. - - 그렇다고 service가 가지는 것도 이상해. - Calculator도 이상해. - - 검증을 정보를 가진 객체가 한다고 하자. 그럼 생성하는 애가 잘못된 애를 주면, 정보 주체는 검증 -> 다시 생성 주체 호출 -> 값 받기 이 과정을 반복해야 하는 것인가. -6. 결론: 생성 객체도 검증의 책임을 져야 한다. - - 근데 그럼 사용자가 입력하는 금액의 생성 객체는 Input인데 걔가 검증을 들고 있나? - -7. Service의 createLotteries는 금액 -> 개수 -> 로또 발급 + Receipt 작성까지 해야 하는데 - service의 구조상, 함수는 하나의 결과값을 return해줘야 하는데 List를 return 해주고 나면, - Receipt 작성은 어떻게 하는가? 함수를 또 따로 빼기엔 너무 구조가 겹치는데? - 뿐만 아니라 추후에 상금과 수익률을 넣어주는 것도 문제다. - 지금 구조에선, Receipt -> 상금, 수익률 계산 함수 -> 계산 -> 새로운 Receipt 기존의 정보 넣어서 생성 - -> 상금, 수익률 입력 -> 다시 return ; 미친 프로세스를 해야한다. - - -8. 해결 방법 - - User 만들자. 그래서 하위에 Receipt, Result, Lotteries를 다 가지고 있자. - 그래서 createLotteries에 User를 만들어 버리자. Result는 걍 초기화정도만 하고.. - - createLotteries를 더 쪼개는 방법. - - calculateAmountOfLotteriesWithMoney: 돈 -> 개수 - - createLotteriesWithAmount: 개수 -> 로또 생성 - - 최종 해결 방법: - - 우선 Receipt와 Result를 쪼개야 한다. - - createLotteries도 더 쪼개야 한다. createReceipt & createLotteries - - 그럼 이제 문제는 createLotteries를 할 때 Receipt을 줘서 할 것인가? - 아니면, Receipt의 개수(int, Receipt.getLotteriesAmount())만 떼어 줄 것인가? - 둘 다 문제가 있다. Receipt은 몰라도 되는 정보를 주고, 종속성에 문제가 생기고, - 개수를 주는 건, 나중에 컨트롤러에서 service의 함수에다가 Receipt.get~~를 써야한다. - - 결론: 아무리 컨트롤러에서 get하는게 적절해 보이지 않아도 Receipt를 넘기는 건 좀... - -9. 각 등수별 로또의 개수를 어떻게 저장하고 누가 정리할 것인가? - - 각 로또들이 등수를 계산해서 내는 건 말이 안돼. 얘가 너무 많은 책임을 가져. - 그럼 그냥 매칭 개수 정도만 return 해줄텐데.. 누군가는 이 정보들을 정리해야 돼. - service는 안돼. calculator가 하기에도 좀 그래.. - - 정보 정리는?? 그냥 리스트로 하기엔 각 인덱스가 몇등을 의미하는지 알기가 어렵다... - 물론 그건 규칙으로 만들 수 있지만 그리 직관적이지도 않고:: GPT한테 시키자. - -10. 해결 방법 - - 우선 Rank enum을 만들어서 enum으로 등수를 저장하자. - - 비교 -> Map 형태로 저장하자. - - Map -> Result 로 구현하자. 즉, 기존의 createResult를 두 단계로 쪼갰다. - - 문제점: service가 이걸 정리하는 과정이 매우 불편하다. 별로 적절해 보이지 않는다. - 그렇다고 lotteries 라는 객체를 만들어서 걔가 lotto를 리스트로 들고, - lotto 묶음의 결과를 가지고 다니는건 적절한가? - - 일단 지금 방식대로 하고 나중에 refactoring 해야 할 듯. - - 이런 설계가 나온 배경은 하나의 도메인에 너무 많은 객체들이 있다. 또 생성하는 것에 솔직히 부담감을 느낀다. - - -11. 문제: controller의 역할 = 도메인 외부와 내부의 연결 + 흐름 관리 \ No newline at end of file diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index 63cae5efb8..c5af2dd968 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -2,7 +2,6 @@ public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 Controller controller = new Controller(); controller.run(); diff --git a/src/main/java/lotto/Calculator.java b/src/main/java/lotto/Calculator.java index 120b32ce18..e3d81f9197 100644 --- a/src/main/java/lotto/Calculator.java +++ b/src/main/java/lotto/Calculator.java @@ -6,7 +6,7 @@ public class Calculator { public int calculateAmountOfLotteriesWithMoney(int money) { return money / 1000; - // magic number 처리 + // TODO: magic number 처리 } public Long calculatePrize(Map rankCount) { diff --git a/src/main/java/lotto/ErrorType.java b/src/main/java/lotto/ErrorType.java index 150271d138..a1808c1deb 100644 --- a/src/main/java/lotto/ErrorType.java +++ b/src/main/java/lotto/ErrorType.java @@ -2,6 +2,8 @@ public enum ErrorType { + // TODO: Error Type 설정 및 Handler 구현 + DUPLICATED_NUMBERS("[ERROR] 중복된 숫자는 허용되지 않습니다"), LOTTO_COUNTS("[ERROR] 로또 번호는 6개여야 합니다."), WRONG_AMOUNT_OF_MONEY("[ERROR] 금액은 1000원 단위여야 합니다."); diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java index 3745df4620..c075d5492c 100644 --- a/src/main/java/lotto/Lotto.java +++ b/src/main/java/lotto/Lotto.java @@ -6,7 +6,6 @@ public class Lotto { private final List numbers; - // indent 2개 이하 지키기... // TODO: Lotto랑 WinningLotto가 유사한 면이 있음. abstract class나 interface를 사용하는 것을 고민해봐야 함. public Lotto(List numbers) { diff --git a/src/main/java/lotto/Output.java b/src/main/java/lotto/Output.java index d87723968c..ffc4105e1a 100644 --- a/src/main/java/lotto/Output.java +++ b/src/main/java/lotto/Output.java @@ -9,7 +9,6 @@ public void printPurchasedCount(int count) { System.out.println(count + "개를 구매했습니다."); } - // 로또 번호는 이미 오름차순 정렬된 List라고 가정 public void printLotteries(List> lotteries) { for (List numbers : lotteries) { System.out.println(numbers); diff --git a/src/main/java/lotto/Service.java b/src/main/java/lotto/Service.java index 750d337d81..050237e9db 100644 --- a/src/main/java/lotto/Service.java +++ b/src/main/java/lotto/Service.java @@ -19,7 +19,6 @@ public Receipt createReceipt(int amountOfMoney) { } public List createLotteries(int amountOfLotteries) { - // 개수 -> 로또 발급! List lotteries = new ArrayList<>(); @@ -29,7 +28,6 @@ public List createLotteries(int amountOfLotteries) { lotteries.add(lotto); } - // perhaps,, lotteries의 개수 검증..?? return lotteries; } diff --git a/src/main/java/lotto/WinningLotto.java b/src/main/java/lotto/WinningLotto.java index e74c3d6354..635a4457a1 100644 --- a/src/main/java/lotto/WinningLotto.java +++ b/src/main/java/lotto/WinningLotto.java @@ -7,7 +7,7 @@ public class WinningLotto { private int bonusNumber; public WinningLotto(List numbers, int bonusNumber) { - // 최후의 보루로서 검증이 필요함. + // TODO: validate 필요함. this.numbers = numbers; this.bonusNumber = bonusNumber; From 8546ee3b7b973bb57b321b7bcbdeb88f01a99881 Mon Sep 17 00:00:00 2001 From: hughryu1125 Date: Sat, 21 Feb 2026 12:42:24 +0900 Subject: [PATCH 3/5] =?UTF-8?q?chore:TODO=20=EC=9E=91=EC=84=B1=20=EB=B0=8F?= =?UTF-8?q?=20=EC=9D=98=EB=AF=B8=20=EC=97=86=EB=8A=94=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 9 --------- src/main/java/lotto/Controller.java | 2 -- src/main/java/lotto/Input.java | 1 + src/main/java/lotto/Service.java | 2 ++ 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/docs/README.md b/docs/README.md index adaa352e70..e301b4571d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -40,15 +40,6 @@ validator를 따로 빼서 클래스로 갖고 있어야하나.. - 번호 검증 함수(중복) -### 3. User - - 총 사용 금액 - - 로또들 - - 총 당첨 금액 - - -### 4. LottoBox - - 랜덤 넘버 생성 - - 위닝 로또 ### 5. Input / Output - 입력과 출력만 담당 diff --git a/src/main/java/lotto/Controller.java b/src/main/java/lotto/Controller.java index a6805ee5a9..9f7c3b18f9 100644 --- a/src/main/java/lotto/Controller.java +++ b/src/main/java/lotto/Controller.java @@ -5,7 +5,6 @@ import java.util.Map; public class Controller { - // input / output과 service 연결 private Input input = new Input(); private Output output = new Output(); private Service service = new Service(); @@ -54,7 +53,6 @@ private WinningLotto createWinningLotto() { } private Map createRankCount(WinningLotto winningLotto, List lotteries) { - //굳이 이긴 하다.. Map rankCounts = service.createRankCounts(winningLotto, lotteries); output.printWinningStatistics(rankCounts); diff --git a/src/main/java/lotto/Input.java b/src/main/java/lotto/Input.java index b18297187c..a27e1e1d79 100644 --- a/src/main/java/lotto/Input.java +++ b/src/main/java/lotto/Input.java @@ -7,6 +7,7 @@ public class Input { + //TODO: 1000원 단위 검증 도입. public int readPurchaseAmount() { System.out.println("구입금액을 입력해 주세요."); String input = Console.readLine(); diff --git a/src/main/java/lotto/Service.java b/src/main/java/lotto/Service.java index 050237e9db..001ad0a5b5 100644 --- a/src/main/java/lotto/Service.java +++ b/src/main/java/lotto/Service.java @@ -6,6 +6,8 @@ import java.util.Map; public class Service { + //TODO: AppConfig 도입하기 + Calculator calculator = new Calculator(); RandomNumberCreator randomNumberCreator = new RandomNumberCreator(); From f4a82000237e53a1a19e3b007f754e597f5790ec Mon Sep 17 00:00:00 2001 From: hughryu1125 Date: Sat, 21 Feb 2026 17:05:06 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20=EB=A7=A4=EC=A7=81=20=EB=84=98?= =?UTF-8?q?=EB=B2=84=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/Calculator.java | 3 +-- src/main/java/lotto/Lotto.java | 7 +++++-- src/main/java/lotto/Rank.java | 4 ++++ src/main/java/lotto/Receipt.java | 2 ++ src/main/java/lotto/Service.java | 12 ++++++------ 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/java/lotto/Calculator.java b/src/main/java/lotto/Calculator.java index e3d81f9197..0921990329 100644 --- a/src/main/java/lotto/Calculator.java +++ b/src/main/java/lotto/Calculator.java @@ -5,8 +5,7 @@ public class Calculator { public int calculateAmountOfLotteriesWithMoney(int money) { - return money / 1000; - // TODO: magic number 처리 + return money / Receipt.LOTTO_PRICE; } public Long calculatePrize(Map rankCount) { diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java index c075d5492c..38e0350502 100644 --- a/src/main/java/lotto/Lotto.java +++ b/src/main/java/lotto/Lotto.java @@ -1,11 +1,14 @@ package lotto; -import java.util.ArrayList; import java.util.List; public class Lotto { private final List numbers; + public static final int MINIMUM_NUMBER = 1; + public static final int MAXIMUM_NUMBER = 45; + public static final int NUMBERS_COUNT = 6; + // TODO: Lotto랑 WinningLotto가 유사한 면이 있음. abstract class나 interface를 사용하는 것을 고민해봐야 함. public Lotto(List numbers) { @@ -41,7 +44,7 @@ public boolean matchBonusNumber(int bonusNumber) { } private void validateCount(List numbers) { - if (numbers.size() != 6) { + if (numbers.size() != NUMBERS_COUNT) { throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다."); } } diff --git a/src/main/java/lotto/Rank.java b/src/main/java/lotto/Rank.java index 57803f3a7d..bee13ca1b8 100644 --- a/src/main/java/lotto/Rank.java +++ b/src/main/java/lotto/Rank.java @@ -24,4 +24,8 @@ public enum Rank { public long getPrize() { return prize; } + + public int getMatchCount() { + return matchCount; + } } diff --git a/src/main/java/lotto/Receipt.java b/src/main/java/lotto/Receipt.java index db0eba68bc..76ab745ccf 100644 --- a/src/main/java/lotto/Receipt.java +++ b/src/main/java/lotto/Receipt.java @@ -3,6 +3,8 @@ import java.util.List; public class Receipt { + public static final int LOTTO_PRICE = 1000; + private int amountOfMoney; private int amountOfLotteries; diff --git a/src/main/java/lotto/Service.java b/src/main/java/lotto/Service.java index 001ad0a5b5..b2a7628024 100644 --- a/src/main/java/lotto/Service.java +++ b/src/main/java/lotto/Service.java @@ -73,21 +73,21 @@ public Result createResult(Map rankCount, int amountOfMoney){ return result; } + // TODO: refactoring 해야 함. service가 rank를 결정해주는게 적절하지 않음. private Rank determineRank(int matchCount, boolean bonusMatch) { - // 맘에 들지 않지만.. 얘를 누가 책임져야 할 지 더 고민해보자. - if (matchCount == 6) { + if (matchCount == Rank.First.getMatchCount()) { return Rank.First; } - if (matchCount == 5 && bonusMatch) { + if (matchCount == Rank.Second.getMatchCount() && bonusMatch) { return Rank.Second; } - if (matchCount == 5) { + if (matchCount == Rank.Third.getMatchCount()) { return Rank.Third; } - if (matchCount == 4) { + if (matchCount == Rank.Fourth.getMatchCount()) { return Rank.Fourth; } - if (matchCount == 3) { + if (matchCount == Rank.Fifth.getMatchCount()) { return Rank.Fifth; } return Rank.MISS; From 7d796fad65b1b10b2510d5b523b81bf70227795e Mon Sep 17 00:00:00 2001 From: hughryu1125 Date: Sat, 21 Feb 2026 17:07:08 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=EB=8B=B9=EC=B2=A8=20=EB=A1=9C=EB=98=90=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8,=20=EB=B3=B4=EB=84=88=EC=8A=A4=20=EB=B2=88=ED=98=B8?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=20=EC=8B=9C=EC=9D=98=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EB=B0=8F=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=EC=9E=85=EB=A0=A5=20=EA=B8=88=EC=95=A1=201000?= =?UTF-8?q?=EC=9B=90=20=EB=8B=A8=EC=9C=84=20=EA=B2=80=EC=A6=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/Input.java | 18 +++++++++++++--- src/main/java/lotto/RandomNumberCreator.java | 12 +---------- src/main/java/lotto/WinningLotto.java | 22 +++++++++++++++++++- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/main/java/lotto/Input.java b/src/main/java/lotto/Input.java index a27e1e1d79..19ac2a5b0c 100644 --- a/src/main/java/lotto/Input.java +++ b/src/main/java/lotto/Input.java @@ -7,7 +7,6 @@ public class Input { - //TODO: 1000원 단위 검증 도입. public int readPurchaseAmount() { System.out.println("구입금액을 입력해 주세요."); String input = Console.readLine(); @@ -20,7 +19,12 @@ public int readPurchaseAmount() { throw new IllegalArgumentException("[ERROR] 구입 금액은 숫자여야 합니다."); } - return Integer.parseInt(input); + int amountOfMoney = Integer.parseInt(input); + + if (amountOfMoney % Receipt.LOTTO_PRICE !=0) { + throw new IllegalArgumentException("[ERROR] 구입 금액은 1000원 단위여야 합니다."); + } + return amountOfMoney; } public List readWinningNumbers() { @@ -32,7 +36,7 @@ public List readWinningNumbers() { } String[] tokens = input.split(","); - if (tokens.length != 6) { + if (tokens.length != Lotto.NUMBERS_COUNT) { throw new IllegalArgumentException("[ERROR] 당첨 번호는 6개여야 합니다."); } @@ -45,6 +49,14 @@ public List readWinningNumbers() { numbers.add(Integer.parseInt(value)); } + for (int i = 0; i < numbers.size() - 1; i++) { + List restNumbers = numbers.subList(i + 1, numbers.size() - 1); + + if (restNumbers.contains(numbers.get(i))) { + throw new IllegalArgumentException("[ERROR] 당첨 번호들은 중복되면 안됩니다."); + } + } + return numbers; } diff --git a/src/main/java/lotto/RandomNumberCreator.java b/src/main/java/lotto/RandomNumberCreator.java index a714dac82a..20839e36cf 100644 --- a/src/main/java/lotto/RandomNumberCreator.java +++ b/src/main/java/lotto/RandomNumberCreator.java @@ -4,18 +4,8 @@ public class RandomNumberCreator { public List createRandomLottoNumbers() { - List numbers = camp.nextstep.edu.missionutils.Randoms.pickUniqueNumbersInRange(1,45,6); + List numbers = camp.nextstep.edu.missionutils.Randoms.pickUniqueNumbersInRange(Lotto.MINIMUM_NUMBER,Lotto.MAXIMUM_NUMBER,Lotto.NUMBERS_COUNT); return numbers; } - - public int createUniqueRandomNumber(List excludedNumbers) { - int number = camp.nextstep.edu.missionutils.Randoms.pickNumberInRange(1, 45); - - while(!excludedNumbers.contains(number)){ - number = camp.nextstep.edu.missionutils.Randoms.pickNumberInRange(1, 45); - } - - return number; - } } diff --git a/src/main/java/lotto/WinningLotto.java b/src/main/java/lotto/WinningLotto.java index 635a4457a1..2eeab77f1e 100644 --- a/src/main/java/lotto/WinningLotto.java +++ b/src/main/java/lotto/WinningLotto.java @@ -1,5 +1,6 @@ package lotto; +import java.util.ArrayList; import java.util.List; public class WinningLotto { @@ -7,7 +8,8 @@ public class WinningLotto { private int bonusNumber; public WinningLotto(List numbers, int bonusNumber) { - // TODO: validate 필요함. + validateDuplicationInNumbers(); + validateDuplicatedBonusNumberInNumbers(); this.numbers = numbers; this.bonusNumber = bonusNumber; @@ -20,4 +22,22 @@ public List getNumbers() { public int getBonusNumber() { return bonusNumber; } + + private void validateDuplicationInNumbers() { + for (int i = 0; i < numbers.size() -1; i++) { + List restNumbers = numbers.subList(i + 1, numbers.size() - 1); + for (int number : numbers) { + if (restNumbers.contains(number)) { + throw new IllegalArgumentException("[ERROR] 중복되는 번호가 있을 수 없습니다."); + } + } + } + + } + + private void validateDuplicatedBonusNumberInNumbers() { + if (numbers.contains(bonusNumber)) { + throw new IllegalArgumentException("[ERROR] 보너스 숫자는 당첨 숫자들과 겹칠 수 없습니다."); + } + } }