-
Notifications
You must be signed in to change notification settings - Fork 108
[SCG] 양호준 미션 3~4 단계 제출합니다 #190
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: hojunyang
Are you sure you want to change the base?
Changes from all commits
dabf6e5
4d9915f
9e5bc4a
bf32388
b423ec6
4c861e7
6332156
3e117bb
363a535
e28a55b
d088321
3c4c833
2c21e52
9e8bc64
00d773d
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,23 @@ | ||
| # 로또 - 클린 코드 | ||
|
|
||
| # 자바 로또 | ||
|
|
||
| ## 기능 목록 | ||
| - 로또 구입 금액을 입력받는다. | ||
| - 수동 구매 개수를 입력받는다. | ||
| - 수동 로또 번호를 입력받는다. | ||
| - 남은 금액만큼 자동 로또를 발행한다. | ||
| - 발행한 로또 목록을 출력한다. | ||
| - 당첨 번호와 보너스 번호를 입력받는다. | ||
| - 당첨 결과를 집계한다. | ||
| - 수익률을 계산해 출력한다. | ||
|
|
||
| ## 예외 사항 | ||
| - 구입 금액은 1000원 이상이어야 한다. | ||
| - 구입 금액은 1000원 단위여야 한다. | ||
| - 수동 구매 개수는 구매 가능한 로또 수를 초과할 수 없다. | ||
| - 로또 번호는 1~45 사이여야 한다. | ||
| - 로또 번호는 6개여야 한다. | ||
| - 로또 번호는 중복될 수 없다. | ||
| - 보너스 번호는 당첨 번호와 중복될 수 없다. | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import controller.Controller; | ||
| import domain.LottoService; | ||
| import domain.Statistic; | ||
| import domain.RandomLottoGenerator; | ||
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| public class Main { | ||
| public static void main(String[] args) { | ||
| Controller controller = new Controller( | ||
| new InputView(), | ||
| new OutputView(), | ||
| new LottoService(new RandomLottoGenerator()), | ||
| new Statistic() | ||
| ); | ||
| controller.run(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package controller; | ||
|
|
||
| import domain.LottoNumber; | ||
| import domain.LottoService; | ||
| import domain.LottoTicket; | ||
| import domain.Statistic; | ||
| import domain.WinningNumbers; | ||
| import domain.WinningRank; | ||
| import java.util.Map; | ||
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class Controller { | ||
| private final InputView inputView; | ||
| private final OutputView outputView; | ||
| private final LottoService lottoService; | ||
| private final Statistic statistic; | ||
|
|
||
| public Controller(InputView inputView, OutputView outputView, | ||
| LottoService lottoService, Statistic statistic) { | ||
| this.inputView = inputView; | ||
| this.outputView = outputView; | ||
| this.lottoService = lottoService; | ||
| this.statistic = statistic; | ||
| } | ||
|
|
||
| public void run(){ | ||
| int money = inputView.getMoney(); | ||
| int manualCount = inputView.getManualLottoCount(); | ||
| List<List<Integer>> manualTicketNumbers = inputView.getManualTicketNumbers(manualCount); | ||
|
|
||
| List<LottoTicket> allTickets = lottoService.buyTickets(money, manualTicketNumbers); | ||
| outputView.printLottoList(manualCount, allTickets.size(), allTickets); | ||
|
|
||
| List<LottoNumber> winningNumberList = inputView.getWinningNumbers(); | ||
| LottoNumber bonusNumber = inputView.getBonusNumber(); | ||
|
|
||
| WinningNumbers winningNumbers = new WinningNumbers(winningNumberList, bonusNumber); | ||
|
|
||
| Map<WinningRank, Integer> winningResult = statistic.getWinningResult(allTickets, winningNumbers); | ||
| double revenue = statistic.getRevenue(money, winningResult); | ||
|
|
||
| outputView.printResult(winningResult, revenue); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package domain; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public interface LottoGenerator { | ||
| List<LottoNumber> generateNumbers(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package domain; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| public class LottoNumber { | ||
| public static final int MIN_NUMBER = 1; | ||
| public static final int MAX_NUMBER = 45; | ||
|
Comment on lines
+6
to
+7
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으로 열어줄 큰 이유가 없다면 최대한 닫아주는게 좋아보여요. |
||
| private final int value; | ||
|
|
||
| public LottoNumber(int value) { | ||
| validateBound(value); | ||
| this.value = value; | ||
| } | ||
|
|
||
| public int value() { | ||
| return value; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object object) { | ||
| if (this == object) { | ||
| return true; | ||
| } | ||
| if (!(object instanceof LottoNumber lottoNumber)) { | ||
| return false; | ||
| } | ||
| return value == lottoNumber.value; | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(value); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return String.valueOf(value); | ||
| } | ||
|
|
||
| private void validateBound(int value) { | ||
| if (value < MIN_NUMBER || value > MAX_NUMBER) { | ||
| throw new IllegalArgumentException( | ||
| String.format("로또 번호는 %d부터 %d 사이여야 합니다. 입력값: %d", MIN_NUMBER, MAX_NUMBER, value) | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| public class LottoService { | ||
| public static final int TICKET_PRICE = 1000; | ||
| LottoGenerator lottoGenerator; | ||
|
|
||
| public LottoService(LottoGenerator lottoGenerator) { | ||
| this.lottoGenerator = lottoGenerator; | ||
| } | ||
|
|
||
| public List<LottoTicket> buyTickets(int money, List<List<Integer>> manualNumbers) { | ||
| validateMoney(money); | ||
| validateManualCount(money, manualNumbers); | ||
|
|
||
| List<LottoTicket> tickets = new ArrayList<>(generateManualTickets(manualNumbers)); | ||
|
|
||
| int autoCount = calculateAutoCount(money, manualNumbers.size()); | ||
| tickets.addAll(generateAutoTickets(autoCount)); | ||
|
|
||
| return tickets; | ||
| } | ||
|
|
||
| private List<LottoTicket> generateManualTickets(List<List<Integer>> manualNumbers) { | ||
| List<LottoTicket> tickets = new ArrayList<>(); | ||
|
|
||
| for (List<Integer> numbers : manualNumbers) { | ||
| tickets.add(new LottoTicket( | ||
| numbers.stream() | ||
| .map(LottoNumber::new) | ||
| .toList() | ||
| )); | ||
| } | ||
|
|
||
| return tickets; | ||
| } | ||
|
Comment on lines
+26
to
+38
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. LottoNumber를 직접 다 만들어서 LottoTicket한테 '너 이거 써'라고 말하고 있어요. |
||
|
|
||
| private List<LottoTicket> generateAutoTickets(int autoCount) { | ||
| List<LottoTicket> tickets = new ArrayList<>(); | ||
|
|
||
| for (int i = 0; i < autoCount; i++) { | ||
| tickets.add(new LottoTicket(lottoGenerator.generateNumbers())); | ||
| } | ||
|
|
||
| return tickets; | ||
| } | ||
|
Comment on lines
+26
to
48
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. 이 로직은 LottoService가 들고 있는게 좋을까요 LottoTicket이 직접 들고 있는게 좋을까요? |
||
|
|
||
| private int calculateAutoCount(int money, int manualCount) { | ||
| return money / TICKET_PRICE - manualCount; | ||
| } | ||
|
|
||
| private void validateMoney(int money) { | ||
| if (money < TICKET_PRICE) { | ||
| throw new IllegalArgumentException("1000원 이상의 금액을 입력해주세요."); | ||
| } | ||
|
|
||
| if (money % TICKET_PRICE != 0) { | ||
| throw new IllegalArgumentException("1000원 단위의 금액을 입력해주세요."); | ||
| } | ||
| } | ||
|
|
||
| private void validateManualCount(int money, List<List<Integer>> manualNumbers) { | ||
| if (manualNumbers == null) { | ||
| throw new IllegalArgumentException("수동 번호는 비어 있을 수 없습니다."); | ||
| } | ||
|
|
||
| if (manualNumbers.size() > money / TICKET_PRICE) { | ||
| throw new IllegalArgumentException("수동 구매 수량이 구입 금액을 초과할 수 없습니다."); | ||
| } | ||
| } | ||
|
Comment on lines
54
to
72
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. 요런 로직이 Service에 많아지면 점점 코드가 읽기 어려워질 것 같아요. 유지보수하기도 어렵고요. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package domain; | ||
|
|
||
| import java.util.Comparator; | ||
| import java.util.List; | ||
|
|
||
| public class LottoTicket { | ||
| private static final int LOTTO_SIZE = 6; | ||
| List<LottoNumber> lottoNumbers; | ||
|
|
||
| public LottoTicket (List<LottoNumber> lottoNumbers) { | ||
| validateSize(lottoNumbers); | ||
| validateDuplicate(lottoNumbers); | ||
| this.lottoNumbers = lottoNumbers; | ||
| } | ||
|
|
||
| public String toDisplayString() { | ||
| return lottoNumbers.stream() | ||
| .sorted(Comparator.comparingInt(LottoNumber::value)) | ||
| .toString(); | ||
| } | ||
|
Comment on lines
16
to
20
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. toString이 아닌 별도 메서드를 만들어주신 이유가 뭔가요? |
||
|
|
||
| public WinningRank getWinningRank(WinningNumbers winningNumbers) { | ||
| int matchCount = countMatchNumbers(winningNumbers); | ||
| boolean bonusMatched = lottoNumbers.contains(winningNumbers.getBonusNumber()); | ||
| return WinningRank.of(matchCount, bonusMatched); | ||
| } | ||
|
|
||
| private int countMatchNumbers(WinningNumbers winningNumbers) { | ||
| int count = 0; | ||
| for (LottoNumber winningNumber : winningNumbers.getLottoNumbers()) { | ||
| if (lottoNumbers.contains(winningNumber)) { | ||
| count++; | ||
| } | ||
| } | ||
| return count; | ||
| } | ||
|
|
||
| private void validateSize(List<LottoNumber> numbers) { | ||
| if (numbers == null || numbers.size() != LOTTO_SIZE) { | ||
| throw new IllegalArgumentException("로또 번호는 6개여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| private void validateDuplicate(List<LottoNumber> numbers) { | ||
| long distinctCount = numbers.stream() | ||
| .distinct() | ||
| .count(); | ||
|
|
||
| if (distinctCount != LOTTO_SIZE) { | ||
| throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다."); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Random; | ||
|
|
||
| public class RandomLottoGenerator implements LottoGenerator { | ||
| private static final int LOTTO_SIZE = 6; | ||
| private static final int LOTTO_MAX_NUMBER = 45; | ||
| private static final int LOTTO_MIN_NUM = 1; | ||
|
|
||
| Random random = new Random(); | ||
|
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 List<LottoNumber> generateNumbers() { | ||
| List<LottoNumber> numbers = new ArrayList<>(); | ||
|
|
||
| while (numbers.size() < LOTTO_SIZE) { | ||
| LottoNumber lottoNumber = new LottoNumber(generateRandomNumber()); | ||
| if (!numbers.contains(lottoNumber)) { | ||
| numbers.add(lottoNumber); | ||
| } | ||
| } | ||
|
|
||
| return numbers; | ||
| } | ||
|
|
||
| private int generateRandomNumber() { | ||
| return LOTTO_MIN_NUM + random.nextInt(LOTTO_MAX_NUMBER); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package domain; | ||
|
|
||
| import java.util.EnumMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public class Statistic { | ||
|
|
||
| public Map<WinningRank, Integer> getWinningResult(List<LottoTicket> tickets, WinningNumbers winningNumbers) { | ||
| Map<WinningRank, Integer> result = new EnumMap<>(WinningRank.class); | ||
|
|
||
| for (WinningRank rank : WinningRank.values()) { | ||
| result.put(rank, 0); | ||
| } | ||
|
|
||
| for (LottoTicket ticket : tickets) { | ||
| WinningRank rank = ticket.getWinningRank(winningNumbers); | ||
|
|
||
| if (rank == WinningRank.MISS) { | ||
| continue; | ||
| } | ||
|
|
||
| result.put(rank, result.get(rank) + 1); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| public double getRevenue(int money, Map<WinningRank, Integer> result) { | ||
| long totalPrize = 0; | ||
|
|
||
| for (WinningRank rank : WinningRank.values()) { | ||
| totalPrize += (long) result.get(rank) * rank.getPrize(); | ||
| } | ||
|
|
||
| return (double) totalPrize / money; | ||
|
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. 이거 100 곱해줘야 하지 않나요? 큰 상관은 없긴 합니다 ㅎㅎ. |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package domain; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class WinningNumbers { | ||
| private static final int LOTTO_SIZE = 6; | ||
| private final List<LottoNumber> lottoNumbers; | ||
| private final LottoNumber bonusNumber; | ||
|
|
||
| public WinningNumbers(List<LottoNumber> lottoNumbers, LottoNumber bonusNumber) { | ||
| validateSize(lottoNumbers); | ||
| validateDuplicate(lottoNumbers, bonusNumber); | ||
| this.lottoNumbers = lottoNumbers; | ||
| this.bonusNumber = bonusNumber; | ||
| } | ||
|
|
||
| public List<LottoNumber> getLottoNumbers() { | ||
| return lottoNumbers; | ||
| } | ||
|
|
||
| public LottoNumber getBonusNumber() { | ||
| return bonusNumber; | ||
| } | ||
|
|
||
| private void validateSize(List<LottoNumber> numbers) { | ||
| if (numbers == null || numbers.size() != LOTTO_SIZE) { | ||
| throw new IllegalArgumentException("당첨 번호는 6개여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| private void validateDuplicate(List<LottoNumber> numbers, LottoNumber bonusNumber) { | ||
| if (numbers.contains(bonusNumber)) { | ||
| throw new IllegalArgumentException("보너스 번호는 당첨 번호와 중복될 수 없습니다."); | ||
| } | ||
|
|
||
| long distinctCount = numbers.stream() | ||
| .distinct() | ||
| .count(); | ||
| if (distinctCount != LOTTO_SIZE) { | ||
| throw new IllegalArgumentException("당첨 번호는 중복될 수 없습니다."); | ||
| } | ||
| } | ||
|
Comment on lines
+25
to
+42
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. LottoTicket과 비슷한 로직과 검증이 있어요. |
||
| } | ||
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.
컬렉션과 배열은 그 개수를 한정짓지 못한다는 문제가 있어요. 이것도 처음보는 사람이라면 6개가 나온다는 사실을 몰랐을 수도 있습니다.
인터페이스를 설계할 때에는 '그 구현체가 앞으로 절대 잘못 구현되지 않을 정도'로 정확하고 명확하게 구현해주는게 좋습니다.