-
Notifications
You must be signed in to change notification settings - Fork 107
[그리디] 김하은 로또 미션 1,2단계 제출합니다. #155
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
Changes from all commits
c3baf1e
fbd8146
cde2dfa
0de51be
33ed28c
cfa1bfe
5d6d0cb
dd90144
1f49977
2a497c6
325628b
c8a9957
243ce72
810a0ac
a3b0e97
da40440
76785ae
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,8 @@ | ||
| package lotto; | ||
|
|
||
| public class Application { | ||
| public static void main(String[] args) { | ||
| LottoGame lottoGame = new LottoGame(); | ||
| lottoGame.run(); // static이 아닌 메서드를 호출! | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package lotto; | ||
|
|
||
| import lotto.domain.*; | ||
| import lotto.view.InputView; | ||
| import lotto.view.OutputView; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class LottoGame { | ||
| private static final int LOTTO_PRICE = 1000; | ||
|
|
||
| public void run() { | ||
| int money = Integer.parseInt(InputView.inputMoney()); | ||
| LottoTickets tickets = purchase(money); | ||
| OutputView.printTickets(tickets); | ||
|
|
||
| Lotto winningLotto = askWinningLotto(); | ||
| showResult(tickets, winningLotto, money); | ||
| } | ||
|
Comment on lines
+12
to
+19
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. 이제 LottoGame을 다시 보면 중요 로직은 도메인에 있기 때문에 컨트롤러는 마치 관리자처럼 객체에게 일을 시키는 존재로 변하게 되는 것을 알 수 있습니다 여기서 좀 더 깔끔한 코딩을 가져가자면 View와 Controller 사이에 String 대신 Integer, List로 변환해주는 클래스도 있으면 좋다는 생각이 드네요. 그러면 컨트롤러는 데이터 가공할 필요도 없이 완전히 객체들의 행동을 연결시키는 역할만 수행할 것 같아요 |
||
|
|
||
| private LottoTickets purchase(int money) { | ||
| int count = money / LOTTO_PRICE; | ||
| OutputView.printTicketCount(count); | ||
| return LottoTickets.generate(count); | ||
| } | ||
|
|
||
| private Lotto askWinningLotto() { | ||
| String input = InputView.inputWinningNumbers(); | ||
| List<Integer> numbers = java.util.Arrays.stream(input.split(",")) | ||
| .map(String::trim) | ||
| .map(Integer::parseInt) | ||
| .collect(java.util.stream.Collectors.toList()); | ||
| return Lotto.from(numbers); | ||
| } | ||
|
|
||
| private void showResult(LottoTickets tickets, Lotto winningLotto, int money) { | ||
| LottoResult lottoResult = new LottoResult(tickets.matchAll(winningLotto)); | ||
| double yield = lottoResult.calculateYield(money); | ||
|
|
||
| OutputView.printStatistics(lottoResult.getResult(), yield); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package lotto.domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public class Lotto { | ||
| private static final int LOTTO_SIZE = 6; | ||
| private final List<LottoNumber> numbers; | ||
|
|
||
| private Lotto(List<LottoNumber> numbers) { | ||
| validate(numbers); | ||
| this.numbers = new ArrayList<>(numbers); | ||
| Collections.sort(this.numbers); | ||
| } | ||
|
|
||
| public static Lotto from(List<Integer> numbers) { | ||
| List<LottoNumber> lottoNumbers = numbers.stream() | ||
| .map(LottoNumber::new) | ||
| .collect(Collectors.toList()); | ||
| return new Lotto(lottoNumbers); | ||
| } | ||
|
|
||
| private void validate(List<LottoNumber> numbers) { | ||
| if (numbers.size() != LOTTO_SIZE) { | ||
| throw new IllegalArgumentException("로또 번호는 6개여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| public int countMatch(Lotto winningLotto) { | ||
| return (int) numbers.stream() | ||
| .filter(winningLotto.numbers::contains) | ||
| .count(); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return numbers.toString(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package lotto.domain; | ||
|
|
||
| import java.util.Objects; | ||
| //자바는 integer의 비교 방법은 이미 알고 있지만, 우리는 LottoNumber을 객체로 만들어서 | ||
| //관리하고 있기 때문에, 비교 방법에 대해서 알려줘야 한다. > comparable<LottoNumber> | ||
| public class LottoNumber implements Comparable<LottoNumber> { | ||
|
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로도 미션 진행할 수 있었을텐데 LottoNumber를 따로 만든 이유가 무엇인지 궁금해요
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. int를 그대로 썼다면 번호를 다루는 모든 곳에서 이 숫자가 1에서 45사이인지를 매번 확인해야했지만 LottoNumber 클래스 안에서 검증하게 함으로써 다른 클래스들이 번호의 유효성을 의심하지 않고 자신의 로직에 집중하게 됐습니다. 역할을 나누니 코드가 단순해지고 각 클래스의 역할이 명확해 진 것 같습니다. 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 MIN_NUMBER = 1; | ||
| private static final int MAX_NUMBER = 45; | ||
| private final int number; | ||
|
|
||
| public LottoNumber(int number) { | ||
| validate(number); | ||
| this.number = number; | ||
| } | ||
|
|
||
| private void validate(int number) { | ||
| if (number < MIN_NUMBER || number > MAX_NUMBER) { | ||
| throw new IllegalArgumentException("로또 번호는 1~45 사이여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| //모든 클래스는 자동으로 boolean equals, haseCode, toString을 상속한다. | ||
| //그러므로 LottoNumber도 이미 가지고 있음 | ||
| //우리가 다시 override하는 이유는, 기본 equals는 주소 비교여서다. 우리가 원하는 것은 | ||
| //값이 같으면 같은 객체로 두게끔이다. 그래서 override해서 수정한다. | ||
| //equals가 같으면 hashCode도 같아야 하므로 같이 수정. | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) {//주소가 같아야 동일한게 아니라, 숫자가 같으면 동일함을 알려주기 위한 메서드 | ||
| if (this == o) return true; | ||
| //getClass는 해당 객체의 타입을 알려주는 메서드 | ||
| if (o == null || getClass() != o.getClass()) return false; | ||
| //if문 다 통과했으니 o는 null이 아니고 o의 클래스가 LottoNumber과 같다고 볼 수 있음 | ||
| LottoNumber that = (LottoNumber) o; //형변환. o는 object지만 LottoNumber임에 틀림없으니 형변환을 해준다 | ||
| //that에 저장 | ||
| return number == that.number; //boolean이므로 같으면 true를 다르면 false를 반환함 | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(number); | ||
| } | ||
|
Comment on lines
+28
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. 👍 요거는 동등성과 동일성에 대해 학습하고 진행하신건가요??
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. 네 이 코드를 짜면서 자바의 동일성과 동등성의 차이에 대해 학습했습니다. 동일성은 ==연산자로 두 객체의 메모리 주소가 같은지를 확인하는 것이고 동등성은 equals 메서드를 통해 두 객체가 가진 값이 같은지를 확인하는 것입니다. 로또 게임에서는 서로 다른 위치에 저장된 객체들이어도 숫자가 같다면 같은 번호로 취급해야하므로 equals를 재정의했습니다. |
||
|
|
||
| //compareTo : 정렬 순서가 오름차순임을 알려줌. Integer.compare 하기 때문 | ||
| @Override | ||
| public int compareTo(LottoNumber other) { | ||
| return Integer.compare(this.number, other.number); | ||
| } | ||
|
|
||
| //toString으로 바꿔주지 않는다면 LottoNumber 객체를 출력했을 때 주소값이 출력됨 | ||
| @Override | ||
| public String toString() { | ||
| return String.valueOf(number); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package lotto.domain; | ||
| import java.util.EnumMap; | ||
| import java.util.Map; | ||
|
|
||
| public class LottoResult { | ||
| private final Map<Rank, Long> result; | ||
|
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. 여기 EnumMap을 사용해봐도 좋아보여요~ |
||
|
|
||
| public LottoResult(Map<Rank, Long> result) { | ||
| this.result = new EnumMap<>(result); | ||
| } | ||
|
|
||
| public double calculateYield(int investment) { | ||
| long totalPrize = result.entrySet().stream() | ||
| .mapToLong(entry -> entry.getKey().getWinningMoney() * entry.getValue()) | ||
| .sum(); | ||
| return (double) totalPrize / investment; | ||
| } | ||
|
|
||
| public Map<Rank, Long> getResult() { | ||
| return result; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package lotto.domain; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.EnumMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.IntStream; | ||
|
|
||
| public class LottoTickets { | ||
|
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 final List<Lotto> tickets; | ||
|
|
||
| public LottoTickets(List<Lotto> tickets) { | ||
| this.tickets = tickets; | ||
| } | ||
|
|
||
| public static LottoTickets generate(int count) { | ||
| List<Integer> allNumbers = IntStream.rangeClosed(1, 45).boxed().collect(Collectors.toList()); | ||
| List<Lotto> tickets = IntStream.range(0, count) | ||
| .mapToObj(i -> { | ||
| Collections.shuffle(allNumbers); | ||
| return Lotto.from(allNumbers.subList(0, 6)); | ||
| }) | ||
| .collect(Collectors.toList()); | ||
| return new LottoTickets(tickets); | ||
| } | ||
|
|
||
| public Map<Rank, Long> matchAll(Lotto winningLotto) { | ||
| return tickets.stream() | ||
| .map(ticket -> Rank.valueOf(ticket.countMatch(winningLotto))) | ||
| .collect(Collectors.groupingBy(rank -> rank, () -> new EnumMap<>(Rank.class), Collectors.counting())); | ||
| } | ||
|
|
||
| public List<Lotto> getTickets() { | ||
| return tickets; | ||
| } | ||
|
|
||
| public int size() { | ||
| return tickets.size(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package lotto.domain; | ||
|
|
||
| import java.util.Arrays; | ||
|
|
||
| public enum Rank { | ||
| MISS(0, 0), | ||
| FIFTH(3, 5_000), | ||
| FOURTH(4, 50_000), | ||
| THIRD(5, 1_500_000), | ||
| FIRST(6, 2_000_000_000); | ||
|
|
||
| private final int matchCount; | ||
| private final int winningMoney; | ||
|
|
||
| Rank(int matchCount, int winningMoney) { | ||
| this.matchCount = matchCount; | ||
| this.winningMoney = winningMoney; | ||
| } | ||
|
|
||
| public static Rank valueOf(int matchCount) { | ||
| return Arrays.stream(values()) | ||
| .filter(rank -> rank.matchCount == matchCount) | ||
| .findFirst() | ||
| .orElse(MISS); | ||
| } | ||
|
|
||
| public int getMatchCount() { | ||
| return matchCount; | ||
| } | ||
|
|
||
| public int getWinningMoney() { | ||
| return winningMoney; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package lotto.view; | ||
|
|
||
| import java.util.Scanner; | ||
|
|
||
| public class InputView { | ||
| private static final Scanner scanner = new Scanner(System.in); | ||
|
|
||
| public static String inputMoney() { | ||
| System.out.println("구입금액을 입력해 주세요."); | ||
| return scanner.nextLine(); | ||
| } | ||
|
|
||
| public static String inputWinningNumbers() { | ||
| System.out.println("지난 주 당첨 번호를 입력해 주세요."); | ||
| return scanner.nextLine(); | ||
| } | ||
|
Comment on lines
+8
to
+16
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. 질문InputView에서는 Integer나 List를 반환 할 수도 있어 보이는데 String으로 처리해주신 이유가 있나요?
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. View의 역할에 대한 고민이 있었습니다. view에서 입력값이 숫자인지, 콤마로 구분된 리스트인지 판단하고 변환하는 로직은 비지니스 로직에 가깝다고 판단했고 그래서 view는 사용자가 입력한 문자열을 그대로 전달하는 역할만 맡도록 짰습니다. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package lotto.view; | ||
|
|
||
| import lotto.domain.Lotto; | ||
| import lotto.domain.LottoTickets; | ||
| import lotto.domain.Rank; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public class OutputView { | ||
| public static void printTicketCount(int count) { | ||
| System.out.println("\n" + count + "개를 구매했습니다."); | ||
| } | ||
|
|
||
| public static void printTickets(LottoTickets tickets) { | ||
| for (Lotto ticket : tickets.getTickets()) { | ||
| System.out.println(ticket); | ||
| } | ||
| System.out.println(); | ||
| } | ||
|
|
||
| public static void printStatistics(Map<Rank, Long> result, double yield) { | ||
| System.out.println("\n당첨 통계\n---------"); | ||
| printRank(Rank.FIFTH, result.getOrDefault(Rank.FIFTH, 0L)); | ||
| printRank(Rank.FOURTH, result.getOrDefault(Rank.FOURTH, 0L)); | ||
| printRank(Rank.THIRD, result.getOrDefault(Rank.THIRD, 0L)); | ||
| printRank(Rank.FIRST, result.getOrDefault(Rank.FIRST, 0L)); | ||
| System.out.printf("총 수익률은 %.2f입니다.\n", yield); | ||
| } | ||
|
|
||
| private static void printRank(Rank rank, long count) { | ||
| System.out.printf("%d개 일치 (%d원)- %d개\n", | ||
| rank.getMatchCount(), rank.getWinningMoney(), count); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package lotto.domain; | ||
|
|
||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.ValueSource; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
|
||
| class LottoNumberTest { | ||
|
|
||
| @ParameterizedTest | ||
| @ValueSource(ints = {0, 46, -1}) | ||
| @DisplayName("로또 번호가 1~45 범위를 벗어나면 예외가 발생한다.") | ||
| void invalidNumberRange(int number) { | ||
| assertThatThrownBy(() -> new LottoNumber(number)) | ||
| .isInstanceOf(IllegalArgumentException.class) | ||
| .hasMessage("로또 번호는 1~45 사이여야 합니다."); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package lotto.domain; | ||
|
|
||
| import org.junit.jupiter.api.DisplayNameGeneration; | ||
| import org.junit.jupiter.api.DisplayNameGenerator; | ||
| import org.junit.jupiter.api.Test; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
|
||
| @SuppressWarnings("NonAsciiCharacters") | ||
| @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) | ||
| class LottoTest { | ||
|
|
||
| @Test | ||
| void 로또_번호가_6개가_아니면_예외가_발생한다() { | ||
| // given | ||
| List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); | ||
|
|
||
| assertThatThrownBy(() -> Lotto.from(numbers)) | ||
| .isInstanceOf(IllegalArgumentException.class); | ||
| } | ||
|
|
||
| @Test | ||
| void 당첨_번호와_몇_개의_번호가_일치하는지_계산한다() { | ||
| Lotto ticket = Lotto.from(Arrays.asList(1, 2, 3, 4, 5, 6)); | ||
| Lotto winningLotto = Lotto.from(Arrays.asList(1, 2, 3, 10, 11, 12)); | ||
|
|
||
| int matchCount = ticket.countMatch(winningLotto); | ||
| assertThat(matchCount).isEqualTo(3); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package lotto.domain; | ||
|
|
||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| class RankTest { | ||
|
|
||
| @Test | ||
| @DisplayName("일치 개수에 따라 올바른 Rank를 반환한다.") | ||
| void valueOfTest() { | ||
| assertThat(Rank.valueOf(6)).isEqualTo(Rank.FIRST); | ||
| assertThat(Rank.valueOf(3)).isEqualTo(Rank.FIFTH); | ||
| assertThat(Rank.valueOf(0)).isEqualTo(Rank.MISS); | ||
| } | ||
| } |
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.java와 LottoGame.java를 분리해두면 좋을 것 같은데요
static이 어떤 역할을 수행하는지, 어떤 상황에 사용하는게 좋을까요??
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.
static은 클래스가 메모리에 로드될 때 한번 생성되며, 객체 생성 없이도 사용할 수 있게 해줍니다. static은 상태 없이 기능만 제공하는 클래스에서 사용하기 좋고 모든 객체가 공유해야하는 상수일 때 사용하면 좋습니다.
리뷰어님의 제안대로 application은 프로그램 시작점의 역할만 수행하고 lottogame은 게임의 흐름을 관리하는 객체로 분리하여 역할을 명확히 해보겠습니다.