Skip to content
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

[로또] 박성우 제출합니다. #1

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,55 @@
package lotto;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lotto.domain.Lotto;
import lotto.domain.Money;
import lotto.domain.WinNumbers;
import lotto.domain.lottonumbercreator.AutoLottoNumberCreator;
import lotto.domain.lottonumbercreator.LottoNumberCreator;
import lotto.domain.lottorank.LottoRank;
import lotto.domain.lottorank.LottoRanks;
import lotto.view.InputView;

public class Application {
private static final LottoNumberCreator CREATOR = new AutoLottoNumberCreator();

public static void main(String[] args) {
// TODO: 프로그램 구현
System.out.println("구입금액을 입력해 주세요.");

final Money money = new Money(InputView.requestInteger());
final int lottoCount = money.getLottoCount();
System.out.println(String.format("\n%d개를 구매했습니다.", lottoCount));

final List<Lotto> lottos = getAutoLottos(lottoCount);
lottos.forEach(System.out::println);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InputView는 선언하시고, OutputView는 선언하지 않으신 이유가 있으실까요?
애플리케이션 중간중간 출력하는 로직이 노출돼서 책임이 모호해지는 것 같습니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OutputView를 만든다고해서 출력하는 로직이 크게 달라질 것 같지않아서.,,?
view에는 많이 신경 안썼습니다 ㅠㅠ

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List에 forEach를 쓸 수 있군요...!!


System.out.println("\n당첨 번호를 입력해 주세요.");
final Lotto winLotto = new Lotto(InputView.requestWinNumbers());

System.out.println("\n보너스 번호를 입력해 주세요.");
int bonusBall = InputView.requestInteger();
Comment on lines +28 to +32

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분도 모두 입력받는 로직 같은데 InputView가 아닌 Application에서 구현하신 이유가 있으실까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Application의 실행 흐름을 모두 Application class에 작성하였는데.. 딱히 이유는 없습니다


final WinNumbers winNumbers = new WinNumbers(winLotto, bonusBall);
final LottoRanks lottoRanks = getLottoRanks(lottos, winNumbers);

System.out.println("당첨 통계\n---");
System.out.println(lottoRanks);
System.out.println(String.format("총 수익률은 %.1f%%입니다.", money.getRateOfProfit(lottoRanks.getProfit())));
}

private static List<Lotto> getAutoLottos(int lottoCount) {
return Stream.generate(() -> Lotto.from(CREATOR))
.limit(lottoCount)
.collect(Collectors.toList());
}

private static LottoRanks getLottoRanks(List<Lotto> lottos, WinNumbers winNumbers) {
final List<LottoRank> ranks = lottos.stream()
.map(lotto -> LottoRank.calculateRank(winNumbers, lotto))
.collect(Collectors.toList());

return LottoRanks.from(ranks);
}
}
20 changes: 0 additions & 20 deletions src/main/java/lotto/Lotto.java

This file was deleted.

75 changes: 75 additions & 0 deletions src/main/java/lotto/domain/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package lotto.domain;

import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import lotto.domain.lottonumbercreator.LottoNumberCreator;

public class Lotto {
private static final int LOTTO_NUMBERS_SIZE = 6;
private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;

private final List<Integer> numbers;

public Lotto(List<Integer> numbers) {
validateSize(numbers);
validateDuplicated(numbers);
validateNumbersRange(numbers);
this.numbers = numbers;
}

public static Lotto from(LottoNumberCreator creator) {
return new Lotto(creator.create(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER, LOTTO_NUMBERS_SIZE));
}

private void validateSize(List<Integer> numbers) {
if (numbers.size() != LOTTO_NUMBERS_SIZE) {
throw new IllegalArgumentException(String.format("[ERROR] 로또 숫자의 개수는 %d일 수 없습니다.", numbers.size()));
}
}

private void validateDuplicated(List<Integer> numbers) {
if (numbers.size() != new HashSet<>(numbers).size()) {
throw new IllegalArgumentException("[ERROR] 중복되는 숫자가 존재합니다.");
}
}

private void validateNumbersRange(List<Integer> numbers) {
if (!numbers.stream().allMatch(this::isInRange)) {
throw new IllegalArgumentException(
String.format("[ERROR] %d ~ %d 범위를 넘어가는 숫자가 존재합니다.", MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
);
}
}

private boolean isInRange(int bonusBall) {
return bonusBall >= MIN_LOTTO_NUMBER && bonusBall <= MAX_LOTTO_NUMBER;
}

public boolean isPossible(int bonusBall) {
final boolean isInRange = isInRange(bonusBall);
final boolean isNotLottoNumber = !numbers.contains(bonusBall);

return isInRange && isNotLottoNumber;
}

public int calculateSameLottoNumber(Lotto other) {
return (int) other.numbers.stream()
.filter(this::contain)
.count();
}

public boolean contain(Integer number) {
return this.numbers.contains(number);
}

@Override
public String toString() {
return String.format("[%s]",
this.numbers.stream()
.sorted()
.map(number -> Integer.toString(number))
.collect(Collectors.joining(", ")));
}
}
25 changes: 25 additions & 0 deletions src/main/java/lotto/domain/Money.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package lotto.domain;

public class Money {
private static final int MIN_VALUE = 1000;
private final int value;

public Money(int value) {
validate(value);
this.value = value;
}

private void validate(int value) {
if (value < MIN_VALUE || value % MIN_VALUE != 0) {
throw new IllegalArgumentException(String.format("[ERROR] 금액은 %d원일 수 없습니다.", value));
}
}

public int getLottoCount() {
return this.value / MIN_VALUE;
}

public double getRateOfProfit(int profit) {
return (double) profit * 100 / value;
}
}
28 changes: 28 additions & 0 deletions src/main/java/lotto/domain/WinNumbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package lotto.domain;

public class WinNumbers {

private final Lotto lotto;

private final int bonusBall;

public WinNumbers(Lotto lotto, int bonusBall) {
validateBonusBall(lotto, bonusBall);
this.lotto = lotto;
this.bonusBall = bonusBall;
}

private void validateBonusBall(Lotto lotto, int bonusBall) {
if (!lotto.isPossible(bonusBall)) {
throw new IllegalArgumentException(String.format("[ERROR] %d는 보너스 볼이 될 수 없습니다.", bonusBall));
}
}
Comment on lines +15 to +19

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 보너스 볼에 대한 검증을 로또에 요청했는데, 보너스 볼에 대한 검증은 보너스볼에서 하는게 더 책임이 명확해보이네요!


public int calculateSameLottoNumber(Lotto lotto) {
return this.lotto.calculateSameLottoNumber(lotto);
}

public boolean matchBonus(Lotto lotto) {
return lotto.contain(bonusBall);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package lotto.domain.lottonumbercreator;

import camp.nextstep.edu.missionutils.Randoms;
import java.util.List;

public class AutoLottoNumberCreator implements LottoNumberCreator{
@Override
public List<Integer> create(int minLottoNumber, int maxLottoNumber, int lottoNumbersSize) {
return Randoms.pickUniqueNumbersInRange(minLottoNumber, maxLottoNumber, lottoNumbersSize);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lotto.domain.lottonumbercreator;

import java.util.List;

public interface LottoNumberCreator {
List<Integer> create(int minLottoNumber, int maxLottoNumber, int lottoNumbersSize);
}
43 changes: 43 additions & 0 deletions src/main/java/lotto/domain/lottorank/LottoRank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package lotto.domain.lottorank;

import java.util.Arrays;
import lotto.domain.Lotto;
import lotto.domain.WinNumbers;

public enum LottoRank {
FIRST(2_000_000_000, 6, false),
SECOND(30_000_000, 5,true),
THIRD(1_500_000, 5, false),
FOURTH(50_000, 4, false),
FIFTH(5_000, 3, false),
OTHER(0, 0, false);


private final int prize;
private final int count;
private final boolean bonus;

LottoRank(int prize, int count, boolean bonus) {
this.prize = prize;
this.count = count;
this.bonus = bonus;
}

public static LottoRank calculateRank(WinNumbers winNumbers, Lotto lotto) {
return Arrays.stream(LottoRank.values())
.filter(rank -> rank.count == winNumbers.calculateSameLottoNumber(lotto))
.filter(rank -> !rank.bonus || winNumbers.matchBonus(lotto))
.findAny()
.orElse(LottoRank.OTHER);
}


public int getPrize() {
return prize;
}

@Override
public String toString() {
return String.format("%d개 일치%s (%,d원)", this.count, this.bonus ? ", 보너스 볼 일치" : "", this.prize);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3항 연산자를 쓰지 않는다.

위 요구사항을 만족하지 못하는 것 같습니다!

}
}
39 changes: 39 additions & 0 deletions src/main/java/lotto/domain/lottorank/LottoRanks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package lotto.domain.lottorank;

import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class LottoRanks {

private final Map<LottoRank, Integer> ranks;

private LottoRanks(Map<LottoRank, Integer> ranks) {
this.ranks = ranks;
}

public static LottoRanks from(List<LottoRank> ranks) {
EnumMap<LottoRank, Integer> lottoRankMap = new EnumMap<>(LottoRank.class);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EnumMap이라는 게 있었군요!! 딱 제가 찾던 녀석입니다 ㅜㅜ

Arrays.stream(LottoRank.values()).forEach(lottoRank -> lottoRankMap.put(lottoRank, 0));

ranks.forEach(rank -> lottoRankMap.computeIfPresent(rank, (key, value) -> value + 1));
return new LottoRanks(lottoRankMap);
}

public int getProfit() {
return this.ranks.entrySet()
.stream()
.mapToInt(entry -> entry.getKey().getPrize() * entry.getValue())
.sum();
}

@Override
public String toString() {
return ranks.entrySet()
.stream()
.map(entry -> String.format("%s - %d개", entry.getKey().toString(), entry.getValue()))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.map(entry -> String.format("%s - %d개", entry.getKey().toString(), entry.getValue()))
.map(entry -> String.format("%s - %d개", entry.getKey(), entry.getValue()))

.collect(Collectors.joining("\n"));
Comment on lines +34 to +37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LottoRank의 모든 값들에 대해 출력하고 있어, 당첨되지 않은 내역도 같이 출력되는 것 같습니다!

}
}
9 changes: 9 additions & 0 deletions src/main/java/lotto/domain/rankvalidator/RankValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package lotto.domain.rankvalidator;

import lotto.domain.Lotto;
import lotto.domain.WinNumbers;

public interface RankValidator {

boolean isRanked(WinNumbers winNumbers, Lotto lotto);
}
35 changes: 35 additions & 0 deletions src/main/java/lotto/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package lotto.view;

import camp.nextstep.edu.missionutils.Console;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class InputView {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유틸성 클래스는 기본 생서자를 private로 선언해 객체 생성을 막아주세요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다. 이런 클래스를 잘 선언하지 않아서 기본 생성자를 추가하는게 어색하네요

private static final String DELIMITER = ",";

private InputView() {
throw new UnsupportedOperationException();
}

public static int requestInteger() {
final String input = Console.readLine();
return parseInt(input);
}
Comment on lines +15 to +18

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestIngeter를 구매금액과 보너스번호 입력에서 같이 사용하고 있는데, 둘 중 하나라도 int가 아닌 타입으로 변경된다면 에러가 발생하지 않을까요?
두 입력에 대한 책임을 분리하는 것이 좋다고 생각하는데 어떻게 생각하시나요ㅎㅎ

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞는 얘기인 것 같아요!
그런데 이 미션에서는 view 관련 로직보다는 객체지향적인 코드에 집중하려고 합니다.
분리하는게 살짝 귀찮네여
살짝 핑계 대봤습니다.


public static List<Integer> requestWinNumbers() {
final String input = Console.readLine();

return Arrays.stream(input.split(DELIMITER))
.map(InputView::parseInt)
.collect(Collectors.toList());
}
Comment on lines +20 to +26

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InputView 클래스에서 입력이 아닌 책임도 같이 갖고 있어서 어색한 것 같아요.
그리고 InputView라고 했는데 Input을 받기 위해 사용되는 메시지들은 전부 Application에서 보여주고 있네요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

view 관련 로직은 어느게 맞는지 잘 모르겠어요..


private static int parseInt(String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(String.format("[ERROR] 입력값 %s는 숫자가 아닙니다.", value));
}
}
}
8 changes: 4 additions & 4 deletions src/test/java/lotto/ApplicationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static camp.nextstep.edu.missionutils.test.Assertions.assertRandomUniqueNumbersInRangeTest;
import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class ApplicationTest extends NsTest {
private static final String ERROR_MESSAGE = "[ERROR]";
Expand Down Expand Up @@ -48,10 +49,9 @@ class ApplicationTest extends NsTest {

@Test
void 예외_테스트() {
assertSimpleTest(() -> {
runException("1000j");
assertThat(output()).contains(ERROR_MESSAGE);
});
assertSimpleTest(() -> assertThatThrownBy(() -> runException("1000j"))
.isInstanceOf(IllegalArgumentException.class)
);
Comment on lines +52 to +54

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻

}

@Override
Expand Down
Loading