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

[로또] 신승철 미션 제출합니다. #5

Open
wants to merge 14 commits into
base: jjanggu
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
65 changes: 65 additions & 0 deletions src/test/java/lotto/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package lotto;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;

public class InputView {

private static final Scanner SCANNER = new Scanner(System.in);

public static int inputMoney() {
Copy link

Choose a reason for hiding this comment

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

짱구님 안녕하세요!

혹시 모든 inputView 메소드를 static으로 만드셨는데 어떤 의도이신지 궁금합니다.
또 만약 console이 아니라 다른 방식으로 입력을 받게 된다면 어떻게 구현하실 생각이신가요??

try {
System.out.println("구매금액을 입력해 주세요.");
return Integer.parseInt(SCANNER.nextLine());
} catch (NumberFormatException e) {
throw new NumberFormatException("구매금액은 숫자로 입력해 주세요.");
}
}

public static int inputManualCount() {
try {
System.out.println("수동으로 구매할 로또 수를 입력해 주세요.");
return Integer.parseInt(SCANNER.nextLine());
} catch (NumberFormatException e) {
throw new NumberFormatException("구매금액은 숫자로 입력해 주세요.");
}
}

public static List<Lotto> inputManualLottoNumbers(final int counts) {
System.out.println("수동으로 구매할 로또 수를 입력해 주세요.");
List<Lotto> manualLottos = new ArrayList<>();
for (int i = 0; i < counts; i++) {
manualLottos.add(Lotto.from(inputLottoNumber()));
}
return manualLottos;
}

private static List<LottoNumber> inputLottoNumber() {
String[] input = SCANNER.nextLine().split(",");
return Arrays.stream(input)
.map(String::trim)
.map(Integer::parseInt)
.map(LottoNumber::from)
.collect(Collectors.toList());
}

public static List<LottoNumber> inputWinningLottoNumbers() {
System.out.println("지난 주 당첨 번호를 입력해 주세요.");
return Arrays.stream(SCANNER.nextLine().split(","))
.map(Integer::parseInt)
.map(LottoNumber::from)
.collect(Collectors.toList());
}

public static int inputWinningBonus() {
try {
System.out.println("보너스 볼을 입력해 주세요.");
return Integer.parseInt(SCANNER.nextLine());
} catch (NumberFormatException e) {
throw new NumberFormatException("구매금액은 숫자로 입력해 주세요.");
}
}
}
31 changes: 31 additions & 0 deletions src/test/java/lotto/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package lotto;

import java.util.List;

public class Lotto {

private final List<LottoNumber> lottoNumbers;

private Lotto(final List<LottoNumber> lottoNumbers) {
this.lottoNumbers = lottoNumbers;
}

public static Lotto from(final List<LottoNumber> lottoNumbers) {
LottoValidator.validate(lottoNumbers);
return new Lotto(lottoNumbers);
}

public boolean isContains(final LottoNumber lottoNumber) {
Copy link

Choose a reason for hiding this comment

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

짱구님의 의견이 궁금합니다.

contains나 matches가 들어가는 함수명은 is를 안붙여도 boolean을 리턴한다는게 읽혀질 것 같아서요. 어떻게 생각하시나요?

return lottoNumbers.contains(lottoNumber);
}

public int calculateHitCount(final Lotto lotto) {
return (int) lottoNumbers.stream()
.filter(lotto::isContains)
.count();
}

public List<LottoNumber> lottoNumbers() {
return this.lottoNumbers;
Copy link

Choose a reason for hiding this comment

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

다른 곳 처럼 원본 객체 말고 복사본을 리턴해주세용

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

public class LottoApplication {

public static void main(String[] args) {
final LottoController lottoController = new LottoController();
lottoController.run();
}
}
26 changes: 26 additions & 0 deletions src/test/java/lotto/LottoController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lotto;

public class LottoController {

public void run() {
try {
final Money money = Money.from(InputView.inputMoney());
final LottoVendor lottoVendor = LottoVendor.from(money);

final int manualCount = InputView.inputManualCount();
final Lottos manualLottos = lottoVendor.purchaseManualLottos(InputView.inputManualLottoNumbers(manualCount));
final Lottos autoLottos = lottoVendor.purchaseAutoLottos();
OutputView.printLottoPurchaseResult(manualLottos, autoLottos);

final Lotto lotto = Lotto.from(InputView.inputWinningLottoNumbers());
final LottoNumber bonusLottoNumber = LottoNumber.from(InputView.inputWinningBonus());
final WinningLotto winningLotto = WinningLotto.of(lotto, bonusLottoNumber);

final LottoResult lottoResult = LottoResult.of(manualLottos, autoLottos, winningLotto);
OutputView.printLottoResult(lottoResult);
} catch (IllegalArgumentException e) {
Copy link

Choose a reason for hiding this comment

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

예외 처리를 프록시 패턴을 사용해서 구현하면
책임을 나눌 수 있어서 좋더라구요! (저는 예외처리를 따로 안하긴했습니다...)

OutputView.printExceptionMessage(e);
run();
}
}
}
47 changes: 47 additions & 0 deletions src/test/java/lotto/LottoNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package lotto;

import java.util.Objects;

public class LottoNumber {
Copy link

Choose a reason for hiding this comment

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

(개인적인 의견입니다.)
리뷰하다가 느낀건데 LottoNumber객체를 상속해서
보너스 번호, 당첨 번호 객체를 생성하는 건 어떻게 생각하시나요??

그렇게 하면 각 객체에 맞는 책임을 나눠줄 수 있을 것 같아요


public static final int MIN_LOTTO_NUMBER = 1;
public static final int MAX_LOTTO_NUMBER = 45;

private final int value;

private LottoNumber(final int value) {
this.value = value;
}

public static LottoNumber from(final int value) {
validateNumberRange(value);
return new LottoNumber(value);
}

private static void validateNumberRange(final int value) {
if (value < MIN_LOTTO_NUMBER || value > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("로또 번호는 1 ~ 45 사이의 숫자여야 합니다.");
Copy link

Choose a reason for hiding this comment

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

로또 번호가 1 ~ 45가 아닌 다른 범위로 바뀔때 여러 부분을 변경해야할 수 있을 것 같아요!

}
}

public int value() {
return this.value;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final LottoNumber that = (LottoNumber) o;
return value == that.value;
}

@Override
public int hashCode() {
return Objects.hash(value);
}
}
27 changes: 27 additions & 0 deletions src/test/java/lotto/LottoNumberAutoGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package lotto;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class LottoNumberAutoGenerator implements LottoNumberGenerator {

private static final List<Integer> lottoNumbers;

static {
lottoNumbers = IntStream.rangeClosed(LottoNumber.MIN_LOTTO_NUMBER, LottoNumber.MAX_LOTTO_NUMBER)
.boxed()
.collect(Collectors.toList());
}

@Override
public List<LottoNumber> generate() {
Collections.shuffle(lottoNumbers);
return lottoNumbers.stream()
.limit(6)
.sorted()
.map(LottoNumber::from)
.collect(Collectors.toList());
}
}
8 changes: 8 additions & 0 deletions src/test/java/lotto/LottoNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package lotto;

import java.util.List;

public interface LottoNumberGenerator {

List<LottoNumber> generate();
}
17 changes: 17 additions & 0 deletions src/test/java/lotto/LottoNumberTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package lotto;

import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

public class LottoNumberTest {

@ValueSource(ints = {0, 46})
@ParameterizedTest
void 로또_번호는_1부터_45_사이의_숫자여야_한다(final int invalidLottoNumber) {
assertThatThrownBy(() -> LottoNumber.from(invalidLottoNumber))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("로또 번호는 1 ~ 45 사이의 숫자여야 합니다.");
}
}
47 changes: 47 additions & 0 deletions src/test/java/lotto/LottoRank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package lotto;

import java.util.Arrays;

public enum LottoRank {

FIRST(6, 2000000000),
SECOND(5, 1500000),
THIRD(5, 50000),
FOURTH(4, 5000),
FIFTH(3, 0),
UNRANKED(0, 0)
;

private final int hitCount;
private final long reward;

LottoRank(final int hitCount, final long reward) {
this.hitCount = hitCount;
this.reward = reward;
}

public static LottoRank of(final int hitCount, final boolean hasBonusNumber) {
return Arrays.stream(LottoRank.values())
.filter(lottoRank -> lottoRank.isSameHitCount(hitCount))
.filter(lottoRank -> !lottoRank.equals(SECOND) || hasBonusNumber)
Copy link

Choose a reason for hiding this comment

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

보너스 넘버를 가져야 하는 rank가 추가될때마다 조건이 계속 추가 될 수 있을 것 같아요
LottoRank에 보너스 번호와 관련된 변수를 하나 추가 하는 건 어떨까요??

.findFirst()
.orElse(UNRANKED);
}

private boolean isSameHitCount(final int hitCount) {
return this.hitCount == hitCount;
}

public boolean hasReward() {
return this.reward != 0;
}

public int hitCount() {
return this.hitCount;
}

public long reward() {
return this.reward;
}
}

30 changes: 30 additions & 0 deletions src/test/java/lotto/LottoRankTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package lotto;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

public class LottoRankTest {

@CsvSource(value = {
"6, false, FIRST",
"5, true, SECOND",
"5, false, THIRD",
"4, true, FOURTH",
"4, false, FOURTH",
"3, true, FIFTH",
"3, false, FIFTH",
"2, true, UNRANKED",
"2, false, UNRANKED",
"1, true, UNRANKED",
"1, false, UNRANKED",
"0, false, UNRANKED"
})
@ParameterizedTest
void 로또_번호_적중_횟수와_보너스_번호_유무에_따른_등수를_반환한다(final int hitCount, final boolean hasBonusNumber, final LottoRank expected) {
final LottoRank actual = LottoRank.of(hitCount, hasBonusNumber);

assertThat(actual).isEqualTo(expected);
}
}
63 changes: 63 additions & 0 deletions src/test/java/lotto/LottoResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package lotto;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.summingInt;

import java.util.EnumMap;
import java.util.Map;

public class LottoResult {

private final Map<LottoRank, Integer> rankResults;
Copy link

Choose a reason for hiding this comment

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

저는 코드읽으면서 이 Map의 Integer값이 무엇을 의미하는지 OutputView를 가서야 이해가 됐어요.


private LottoResult(final Map<LottoRank, Integer> rankResults) {
this.rankResults = rankResults;
}

public static LottoResult of(final Lottos manualLottos, final Lottos autoLottos, final WinningLotto winningLotto) {
final Map<LottoRank, Integer> manualLottoResults = groupBy(manualLottos, winningLotto);
final Map<LottoRank, Integer> autoLottoResults = groupBy(autoLottos, winningLotto);
final Map<LottoRank, Integer> results = mergeResults(manualLottoResults, autoLottoResults);

return new LottoResult(results);
}

private static Map<LottoRank, Integer> groupBy(final Lottos autoLottos, final WinningLotto winningLotto) {
Copy link

Choose a reason for hiding this comment

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

변수명 autoLottos 가 아닌 다른 걸 사용하는게 적절해보입니다!
manualLotto가 들어오는 경우도 있으니까요

return autoLottos.lottos().stream()
.collect(groupingBy(winningLotto::calculateRank, summingInt(value -> 1)));
}

private static Map<LottoRank, Integer> mergeResults(Map<LottoRank, Integer> manualResults, Map<LottoRank, Integer> autoResults) {
Map<LottoRank, Integer> combinedResults = new EnumMap<>(LottoRank.class);

for (LottoRank rank : LottoRank.values()) {
combinedResults.put(rank, manualResults.getOrDefault(rank, 0) + autoResults.getOrDefault(rank, 0));
}

return combinedResults;
}

public int CountBy(final LottoRank lottoRank) {
return this.rankResults.getOrDefault(lottoRank, 0);
}

public double calculateProfitRate() {
final long totalReward = calculateTotalReward();
final int purchaseAmount = calculatePurchaseAmount();

return totalReward / (double) purchaseAmount;
}

private long calculateTotalReward() {
return this.rankResults.entrySet().stream()
.map(rankResult -> rankResult.getKey().reward() * rankResult.getValue())
.reduce(0L, Long::sum);
}

private int calculatePurchaseAmount() {
final int numberOfPurchases = this.rankResults.values().stream()
.mapToInt(i -> i)
.sum();
return numberOfPurchases * LottoVendor.LOTTO_PRICE;
}
}
Loading