-
Notifications
You must be signed in to change notification settings - Fork 92
[이예진] 로또 1,2단계 구현 #126
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: yaevrai
Are you sure you want to change the base?
[이예진] 로또 1,2단계 구현 #126
Changes from 4 commits
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,70 @@ | ||
| # java-lotto : 로또 - 클린코드 | ||
|
|
||
| ## Level1. 로또 자동 구매 | ||
|
|
||
| ### - 요구사항 | ||
|
|
||
| - [x] 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다. | ||
| - [x] 로또 1장의 가격은 1000원이다. | ||
|
|
||
| ### - 새로운 프로그래밍 요구사항 | ||
|
|
||
| - [x] 배열 대신 컬렉션을 사용한다. | ||
| - [x] 줄여 쓰지 않는다(축약 금지). | ||
| - [x] 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다. | ||
| - [x] 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. | ||
|
|
||
| ## Level2. 로또 당첨 | ||
|
|
||
| ### - 요구사항 | ||
|
|
||
| - [x] 로또 당첨 번호를 받아 일치한 번호 수에 따라 당첨 결과를 보여준다. | ||
|
|
||
| ### - 새로운 프로그래밍 요구사항 | ||
|
|
||
| - [x] 모든 원시 값과 문자열을 포장한다. | ||
| - [x] 일급 컬렉션을 쓴다. | ||
|
|
||
| ## Level3. 로또 2등 당첨 | ||
|
|
||
| ### - 요구사항 | ||
|
|
||
| - [ ] 2등을 위한 보너스볼을 추첨한다. | ||
| - [ ] 당첨 통계에 2등을 추가한다. | ||
| - [ ] 2등 당첨 조건은 당첨 번호 5개 일치 + 보너스 볼 일치다. | ||
|
|
||
| ### - 새로운 프로그래밍 요구사항 | ||
|
|
||
| - [ ] Java Enum을 적용한다. | ||
|
|
||
| ## Level4. 로또 수동 구매 | ||
|
|
||
| ### - 요구사항 | ||
|
|
||
| - [ ] 현재 로또 생성기는 자동 생성 기능만 제공한다. 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다. | ||
| - [ ] 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다. | ||
|
|
||
| ## Level5. 리팩토링 | ||
|
|
||
| ### - 새로운 프로그래밍 요구사항 | ||
|
|
||
| - [ ] 기존 프로그래밍 요구사항을 다시 한번 확인하고, 학습 테스트를 통해 학습한 내용을 반영한다. | ||
|
|
||
| ### - 기존 프로그래밍 요구사항 | ||
|
|
||
| - [ ] 자바 코드 컨벤션을 지키면서 프로그래밍한다. | ||
| - [ ] 기본적으로 Java Style Guide을 원칙으로 한다. | ||
| - [ ] indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다. | ||
| - [ ] 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 | ||
| 된다. | ||
| - [ ] 3항 연산자를 쓰지 않는다. | ||
| - [ ] else 예약어를 쓰지 않는다. | ||
| - [ ] else 예약어를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. 힌트: if문에서 값을 반환하는 방식으로 | ||
| 구현하면 else 예약어를 사용하지 않아도 된다. | ||
| - [ ] 배열 대신 컬렉션을 사용한다. | ||
| - [ ] 줄여 쓰지 않는다(축약 금지). | ||
| - [ ] 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다. | ||
| - [ ] 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. | ||
| - [ ] 모든 원시 값과 문자열을 포장한다. | ||
| - [ ] 일급 컬렉션을 쓴다. | ||
| - [ ] Java Enum을 적용한다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package lotto; | ||
|
|
||
| import lotto.view.LottoInputView; | ||
| import lotto.view.LottoOutputView; | ||
|
|
||
| public class LottoController { | ||
|
|
||
| public static void main(String[] args) { | ||
| LottoService service = new LottoService(new LottoInputView(), new LottoOutputView()); | ||
| service.start(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package lotto; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
| import lotto.model.Lotto; | ||
| import lotto.model.LottoGenerator; | ||
| import lotto.model.LottoTicket; | ||
| import lotto.model.MatchCount; | ||
| import lotto.model.Money; | ||
| import lotto.model.WinningNumbers; | ||
| import lotto.model.WinningResult; | ||
| import lotto.view.LottoInputView; | ||
| import lotto.view.LottoOutputView; | ||
|
|
||
| public class LottoService { | ||
|
|
||
| private final LottoInputView inputView; | ||
| private final LottoOutputView outputView; | ||
| private final LottoGenerator lottoGenerator; | ||
|
|
||
| public LottoService(LottoInputView inputView, LottoOutputView outputView) { | ||
| this.inputView = inputView; | ||
| this.outputView = outputView; | ||
| this.lottoGenerator = new LottoGenerator(); | ||
| } | ||
|
|
||
| public void start() { | ||
| Money purchaseMoney = new Money(inputView.inputMoney()); | ||
|
|
||
| List<Lotto> lottos = purchaseLottos(purchaseMoney); | ||
|
|
||
| LottoTicket lottoTicket = purchaseMoney.calculateTicketCount(); | ||
|
||
|
|
||
| outputView.printPurchasedLottoCount(lottoTicket.getCount()); | ||
| outputView.printLottos(lottos); | ||
|
|
||
| WinningNumbers winningNumbers = new WinningNumbers(inputView.inputWinningNumber()); | ||
|
|
||
| WinningResult winningResult = calculateWinningResult(lottos, winningNumbers); | ||
|
|
||
| outputView.printWinningStatistics(purchaseMoney.getValue(), | ||
| winningResult.getWinningStatistics()); | ||
|
||
| } | ||
|
|
||
| private List<Lotto> purchaseLottos(Money money) { | ||
| return lottoGenerator.generate(money.calculateTicketCount().getCount()); | ||
| } | ||
|
|
||
| private WinningResult calculateWinningResult(List<Lotto> lottos, WinningNumbers winningNumbers) { | ||
| List<MatchCount> matchCounts = lottos.stream() | ||
| .map(lotto -> lotto.matchWith(winningNumbers)) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| return new WinningResult(matchCounts); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,26 @@ | ||||||||||||||
| package lotto.model; | ||||||||||||||
|
|
||||||||||||||
| import java.util.List; | ||||||||||||||
|
|
||||||||||||||
| public class Lotto { | ||||||||||||||
|
|
||||||||||||||
| private final LottoNumbers numbers; | ||||||||||||||
|
||||||||||||||
|
|
||||||||||||||
| public Lotto(List<Integer> numbers) { | ||||||||||||||
| this.numbers = new LottoNumbers(numbers); | ||||||||||||||
| } | ||||||||||||||
|
||||||||||||||
| public Lotto(List<Integer> numbers) { | |
| this.numbers = new LottoNumbers(numbers); | |
| } | |
| public Lotto(LottoNumbers LottoNumbers) { | |
| this.numbers = LottoNumbers; | |
| } |
생성자의 인자로 LottoNumbers이 아닌, List<Integer> 타입을 받으신 이유가 있으실까요?
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.
앗 수정했습니다🤣🤣
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package lotto.model; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.IntStream; | ||
|
|
||
| public class LottoGenerator { | ||
|
|
||
| private static final int MAX_NUMBER = 45; | ||
| private static final int LOTTO_SIZE = 6; | ||
|
|
||
| public List<Lotto> generate(int count) { | ||
| return IntStream.range(0, count) | ||
| .mapToObj(i -> new Lotto(pickFirstSixSorted(createShuffledNumbers()))) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| private List<Integer> createShuffledNumbers() { | ||
| List<Integer> numbers = IntStream.rangeClosed(1, MAX_NUMBER) | ||
| .boxed() | ||
| .collect(Collectors.toList()); | ||
| Collections.shuffle(numbers); | ||
| return numbers; | ||
| } | ||
|
|
||
| private List<Integer> pickFirstSixSorted(List<Integer> numbers) { | ||
| return numbers.stream() | ||
| .limit(LOTTO_SIZE) | ||
| .sorted() | ||
| .collect(Collectors.toList()); | ||
| } | ||
| } | ||
|
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. 로또 번호를 생성해 주는 클래스군요! 해당 클래스에서 생성된 로또 번호는 하지만 그 의미는 해당 클래스 내에서만 유효하고, 해당 클래스를 사용하는 곳에서는 그 의미를 찾을 수 없어요. LottoGenerator lottoGenerator = new LottoGenerator();
List<Integer> lottoNumbers = lottoGenerator.generate();
lottoNumbers.add(1);그렇다면 해당 클래스를 사용하는 곳에서도 그 의미를 어떻게 전달할 수 있을까요? 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,26 @@ | ||
| package lotto.model; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.List; | ||
|
|
||
| public class LottoNumbers { | ||
|
|
||
| private static final int LOTTO_SIZE = 6; | ||
| private final List<Integer> numbers; | ||
|
|
||
| public LottoNumbers(List<Integer> numbers) { | ||
| validate(numbers); | ||
| this.numbers = numbers; | ||
| } | ||
|
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.
그 지식 중 하나는 "반드시 6개의 번호를 가진다"에요. 즉, 이 규칙은 절대로 변하면 안 되는 도메인 규칙이에요. 하지만 지금 구현에서는 정말 "6개"의 숫자가 변경되지 않는다고 보장할 수 있을까요? List<Integer> numbers = new ArrayList();
LottoNumbers lottoNumbers = new LottoNumbers(numbers);
numbers.clear();
assertThat(lottoNumbers.getNumbers()).hasSize(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. 거기까지 방어해야한단 생각을 못했던 것 같아요!! |
||
|
|
||
| private void validate(List<Integer> numbers) { | ||
| if (numbers.size() != LOTTO_SIZE) { | ||
| throw new IllegalArgumentException("로또 번호는 6개여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| // 수정 불가능한 숫자 리스트를 반환 | ||
| public List<Integer> getNumbers() { | ||
| return Collections.unmodifiableList(numbers); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package lotto.model; | ||
|
|
||
| public class LottoTicket { | ||
|
|
||
| private final int count; | ||
|
|
||
| public LottoTicket(int count) { | ||
| this.count = count; | ||
| } | ||
|
|
||
| public int getCount() { | ||
| return count; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package lotto.model; | ||
|
|
||
| public class MatchCount { | ||
|
|
||
| private final int count; | ||
|
|
||
| public MatchCount(int count) { | ||
| this.count = count; | ||
| } | ||
|
|
||
| public int getCount() { | ||
| return count; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package lotto.model; | ||
|
|
||
| public class 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.
또한 내부 필드가 final으로 지정되어, 여러 스레드에서 공유해도 안전할 것 같네요! 이러한 객체 구현을 "값 객체(Value Object)"라고 불러요. 값 객체는 상태를 변경 메서드를 제공하지 않으며, 그 자체로 식별성을 가져요. 하지만 지금 구현에서도 그럴까요? Money first = new Money(100);
Money second = new Money(100);
assertThat(first).isEqualTo(second);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. 감사합니다 수정했습니다 !! |
||
|
|
||
| private static final int LOTTO_PRICE = 1000; | ||
| private final int value; | ||
|
||
|
|
||
| public Money(int value) { | ||
| this.value = value; | ||
| } | ||
|
|
||
| public int getValue() { | ||
| return value; | ||
| } | ||
|
|
||
| public LottoTicket calculateTicketCount() { | ||
|
||
| return new LottoTicket(value / LOTTO_PRICE); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package lotto.model; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class WinningNumbers { | ||
|
|
||
| private final LottoNumbers numbers; | ||
|
|
||
| public WinningNumbers(List<Integer> numbers) { | ||
| this.numbers = new LottoNumbers(numbers); | ||
| } | ||
|
|
||
| public List<Integer> getNumbers() { | ||
| return numbers.getNumbers(); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package lotto.model; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public class WinningResult { | ||
|
|
||
| private static final Map<Integer, Integer> PRIZE_MONEY = Map.of( | ||
| 3, 5_000, | ||
| 4, 50_000, | ||
| 5, 1_500_000, | ||
| 6, 2_000_000_000 | ||
| ); | ||
|
||
|
|
||
| private final Map<Integer, Integer> matchResults; | ||
|
|
||
| public WinningResult(List<MatchCount> matchCounts) { | ||
| this.matchResults = calculateResults(matchCounts); | ||
| } | ||
|
|
||
| private Map<Integer, Integer> calculateResults(List<MatchCount> matchCounts) { | ||
| Map<Integer, Integer> results = new HashMap<>(); | ||
| PRIZE_MONEY.keySet().forEach(matchCount -> results.put(matchCount, 0)); | ||
|
|
||
| for (MatchCount matchCount : matchCounts) { | ||
| int count = matchCount.getCount(); | ||
| if (count >= 3) { | ||
| results.put(count, results.get(count) + 1); | ||
| } | ||
| } | ||
|
||
|
|
||
| return results; | ||
| } | ||
|
|
||
| public Map<String, Long> getWinningStatistics() { | ||
| Map<String, Long> statistics = new HashMap<>(); | ||
|
|
||
| matchResults.forEach((matchCount, count) -> | ||
| statistics.put(String.valueOf(matchCount), (long) count)); | ||
|
|
||
| long totalPrize = matchResults.entrySet().stream() | ||
|
||
| .mapToLong(entry -> (long) PRIZE_MONEY.get(entry.getKey()) * entry.getValue()) | ||
|
||
| .sum(); | ||
|
|
||
| statistics.put("total", totalPrize); | ||
| return statistics; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package lotto.view; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Scanner; | ||
|
|
||
| public class LottoInputView { | ||
|
|
||
| public int inputMoney() { | ||
| System.out.println("구입금액을 입력해 주세요."); | ||
| Scanner scanner = new Scanner(System.in); | ||
|
|
||
| return scanner.nextInt(); | ||
| } | ||
|
|
||
| public List<Integer> inputWinningNumber() { | ||
| System.out.println("지난 주 당첨 번호를 입력해 주세요."); | ||
| Scanner scanner = new Scanner(System.in); | ||
|
|
||
| String winningNumbersString = scanner.nextLine(); | ||
| String[] numbers = winningNumbersString.split(","); | ||
| ArrayList<Integer> winningNumbers = new ArrayList<>(); | ||
|
|
||
| for (String num : numbers) { | ||
| winningNumbers.add(Integer.parseInt(num.trim())); | ||
| } | ||
|
|
||
| return winningNumbers; | ||
| } | ||
| } |
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.
지난 미션에선 Main이란 이름의 클래스에서 Controller를 조작하도록 구현했었는데,
Controller가 하는 일과 네이밍이 잘 안맞는 듯해서 이번엔 Controller내 메인 클래스를 두고 프로그램 조작만을 담당하도록 했습니다.!
지난 미션에선 하나의 View 객체를 만들어서 입/출력 관련 역할을 모았었는데요,
스캐너를 사용하는 건 같지만 출력/입력은 다른 역할이라는 생각이 들어 입/출력 View 클래스를 나누어 보았습니다.
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.
예진님이 생각하시는 Controller의 역할은 무엇인가요? 그렇다면 Service의 역할은 무엇인가요?
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.
Controller: 외부 요청을 받고, 그 요청을 처리할 적절한 로직으로 연결해주는 역할Service: 비즈니스 로직을 담당하는 역할이번 미션에선 요청이라고는 할 수 없지만, 실행 및 흐름을 조작을 하는 역할을 하고, 나머지 기능은 서비스단에서 구현하도록 했습니ㄷ!