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

Step4 - 로또(수동) #3238

Open
wants to merge 10 commits into
base: gisungpark
Choose a base branch
from
70 changes: 36 additions & 34 deletions src/main/java/step2/controller/LottoGameController.java
Original file line number Diff line number Diff line change
@@ -1,73 +1,75 @@
package step2.controller;

import step2.domain.LottoCommonValue;
import step2.domain.LottoGames;
import step2.domain.LottoResultReport;
import step2.domain.LottoTicket;
import step2.domain.*;
import step2.view.InputView;
import step2.view.ResultView;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class LottoGameController {

private static final int LOTTO_TICKET_PRICE = 1000;
Copy link

Choose a reason for hiding this comment

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

로또 금액은 비즈니스 영역에 해당하는 값으로 보여지는데 현재 Controller에서 상수로 관리되고 있어요! 적절한 도메인 객체에서 해당 값을 관리하는 것이 어떨까요?

private LottoGames lottoGames = new LottoGames();

public void playLottoGame() {
lxxjn0 marked this conversation as resolved.
Show resolved Hide resolved
Money balance = new Money(InputView.readMoney());

int money = InputView.readAmountOfPurchase();
int manualLottoTicketCount = InputView.readCountOfManualTicket();
List<LottoTicket> manualLottoTickets = buyManualTickets(money, manualLottoTicketCount);
List<LottoTicket> manualLottoTickets = buyManualTickets(balance);
List<LottoTicket> automaticLottoTickets = getBuyAutomaticTickets(balance);

int automaticTicketCount = lottoGames.calculateBuyingTicketCount(money, manualLottoTicketCount);
ResultView.printNumberOfTickets(manualLottoTicketCount, automaticTicketCount);
if (automaticTicketCount == 0 && manualLottoTicketCount == 0) {
ResultView.printNumberOfTickets(manualLottoTickets.size(), automaticLottoTickets.size());
if (manualLottoTickets.size() == 0 && automaticLottoTickets.size() == 0) {
return;
}

List<LottoTicket> automaticLottoTickets = getBuyAutomaticTickets(automaticTicketCount);
ResultView.printLottoTicket(automaticLottoTickets);
LottoTicket winningTicket = readWinningTicket();
int bonusNumber = InputView.readBonusNumber();

ResultView.printBlankLine();
ResultView.printMessage("당첨 통계");
LottoNo bonusNumber = LottoNo.of(InputView.readBonusNumber());
Copy link

Choose a reason for hiding this comment

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

축약어는 최대한 지양하면 좋을 것 같아요!! LottoNumber라는 이름이 좀 더 직관적이고 가독성이 더 좋을 것 같아요!!


LottoResultReport lottoResultReport = getLottoResultReport(manualLottoTickets, automaticLottoTickets, winningTicket, bonusNumber);

ResultView.printResultReport(lottoResultReport);
double profit = lottoResultReport.calculateProfit(manualLottoTicketCount + automaticTicketCount);
ResultView.printMessage("총 수익률은 " + profit + "입니다.");
printResultStatistic(getLottoResultReport(manualLottoTickets, automaticLottoTickets, winningTicket, bonusNumber));
}

private List<LottoTicket> buyManualTickets(int totalMoney, int manualTicketCount) {
if(manualTicketCount * LottoCommonValue.DEFAULT_LOTTO_PRICE.value() > totalMoney) {
throw new IllegalArgumentException("수동으로 구매할 수 있는 티켓 개수를 초과하셨습니다.");
}
private List<LottoTicket> buyManualTickets(Money money) {
int manualTicketCount = getManualTicketCount(money);
money.pay(manualTicketCount * LOTTO_TICKET_PRICE);

ResultView.printMessage("수동으로 구매할 번호를 입력해 주세요");
List<LottoTicket> manualLottoTickets = new ArrayList<>();
for(int i=0; i<manualTicketCount; i++) {
Optional<LottoTicket> lottoTicket = lottoGames.toLottoTicket(InputView.readManualTicketNumbers());
manualLottoTickets.add(lottoTicket.orElseThrow(IllegalArgumentException::new));
List<LottoTicket> manualLottoTickets = new ArrayList<>(manualTicketCount);
for (int i = 0; i < manualTicketCount; i++) {
LottoTicket lottoTicket = lottoGames.toLottoTicket(InputView.readManualTicketNumbers());
manualLottoTickets.add(lottoTicket);
}
return manualLottoTickets;
}
Copy link

Choose a reason for hiding this comment

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

지금은 입력 로직으로 인해 비즈니스 로직과 입력 로직이 많이 엮여있는 것 같아요!! 로직을 절차식으로 작성하려고 하면서 오히려 이 두 로직의 구분이 어려워 진 걸 수도 있어요!!

만약 입력 값을 먼저 다 받아두고 해당하는 입력값을 한번에 LottoGames에 넘겨줘서 로직을 구현할 수도 있고요!! 지금의 로직이 문제가 있는 것은 아니지만 조금 더 뷰의 로직과 도메인의 로직을 구분하고 싶다면 이런 식으로 개선을 해볼 수도 있을 것 같아요!!


private List<LottoTicket> getBuyAutomaticTickets(int automaticTicketCount) {
private int getManualTicketCount(Money money) {
int manualTicketCount = InputView.readCountOfManualTicket();
int cost = manualTicketCount * LottoCommonValue.DEFAULT_LOTTO_NUMBER_COUNT.value();
money.pay(cost);
return manualTicketCount;
}

private List<LottoTicket> getBuyAutomaticTickets(Money money) {
int automaticTicketCount = money.balance() / LOTTO_TICKET_PRICE;
Copy link

Choose a reason for hiding this comment

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

해당 로직은 getter를 사용해서 값을 가져와서 계산을 하고 있어요!! 이렇게 된다면 값을 객체로 감싼 의미가 조금은 퇴색될 수 있는데 어떻게 바꾸면 좋을까요?

money.pay(automaticTicketCount * LOTTO_TICKET_PRICE);
return lottoGames.buyAutomaticLottoTickets(automaticTicketCount);
}

private LottoTicket readWinningTicket() {
ResultView.printMessage("지난 주 당첨 번호를 입력해 주세요");
return lottoGames.toLottoTicket(InputView.readWinningNumbers())
.orElseThrow(() -> new IllegalArgumentException("잘못 입력하셨습니다."));
return lottoGames.toLottoTicket(InputView.readWinningNumbers());
}

private void printResultStatistic(LottoResultReport lottoResultReport) {
ResultView.printBlankLine();
ResultView.printMessage("당첨 통계");
ResultView.printResultReport(lottoResultReport);
ResultView.printMessage("총 수익률은 " + lottoResultReport.calculateProfit() + "입니다.");
}

private LottoResultReport getLottoResultReport(List<LottoTicket> manualLottoTickets,
List<LottoTicket> automaticLottoTickets,
LottoTicket winningTicket, int bonusNumber) {
LottoTicket winningTicket, LottoNo bonusNumber) {

LottoResultReport lottoResultReport = new LottoResultReport();
for (LottoTicket lottoTicket : manualLottoTickets) {
Expand Down
5 changes: 0 additions & 5 deletions src/main/java/step2/domain/LottoCommonValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,4 @@ public enum LottoCommonValue {
public int value() {
return value;
}





}
20 changes: 3 additions & 17 deletions src/main/java/step2/domain/LottoGames.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ public class LottoGames {
public LottoGames() {
}

public int calculateBuyingTicketCount(int money, int manualLottoTicketCount) {
money -= manualLottoTicketCount * LottoCommonValue.DEFAULT_LOTTO_PRICE.value();
return new Integer(money / LottoCommonValue.DEFAULT_LOTTO_PRICE.value());
}

public List<LottoTicket> buyAutomaticLottoTickets(int gameCount) {
List<LottoTicket> lottoTickets = new ArrayList<>(gameCount);
for (int i = 0; i < gameCount; i++) {
Expand All @@ -28,18 +23,8 @@ private LottoTicket createLottoGame() {
return new LottoTicket(lottoNumbers);
}

public Optional<LottoTicket> toLottoTicket(String stringNumber) {
String[] splits = splitByDelimiter(stringNumber);
Set<LottoNo> numbers = toSet(splits);
if (numbers.size() != LottoCommonValue.DEFAULT_LOTTO_NUMBER_COUNT.value()) {
return Optional.empty();
}
return Optional.ofNullable(new LottoTicket(numbers));
}

private String[] splitByDelimiter(String stringNumber) {
stringNumber = stringNumber.replaceAll(" ", "");
return stringNumber.split(",");
public LottoTicket toLottoTicket(String[] splits) {
return new LottoTicket(toSet(splits));
}

private Set<LottoNo> toSet(String[] numbers) {
Expand All @@ -51,6 +36,7 @@ private Set<LottoNo> toSet(String[] numbers) {
}

private Integer toInt(String element) {
element = element.trim();
try {
return Integer.parseInt(element);
} catch (NumberFormatException e) {
Expand Down
13 changes: 9 additions & 4 deletions src/main/java/step2/domain/LottoNo.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.IntStream;

public class LottoNo {
lxxjn0 marked this conversation as resolved.
Show resolved Hide resolved
private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;

private static Map<Integer, LottoNo> lottoNumberCache = new HashMap<>();
private static final Map<Integer, LottoNo> lottoNumberCache = new HashMap<>();
Copy link

Choose a reason for hiding this comment

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

네이밍도 상수 컨벤션에 맞게 바꾸면 좋을 것 같아요!!


static {
IntStream.range(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER + 1)
Expand All @@ -24,8 +23,14 @@ private LottoNo(int number) {
}

public static LottoNo of(int number) {
Optional<LottoNo> lottoNo = Optional.ofNullable(lottoNumberCache.get(number));
return lottoNo.orElseThrow(() -> new IllegalArgumentException("잘못 입력하셨습니다."));
if (isInvalidNumber(number)) {
throw new IllegalArgumentException("잘못 입력하셨습니다.");
}
return lottoNumberCache.get(number);
}

private static boolean isInvalidNumber(int number) {
return number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER;
}

public int number() {
Expand Down
16 changes: 13 additions & 3 deletions src/main/java/step2/domain/LottoResultReport.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,32 @@
public class LottoResultReport {

private Map<Rank, Integer> lottoResultReport;
private int ticketCount;

public LottoResultReport() {
lottoResultReport = new HashMap<>();
ticketCount = 0;
}

public int recordRank(Rank rank) {
return lottoResultReport.compute(rank, (key, value) -> value == null ? 1 : value + 1);
ticketCount += 1;
return lottoResultReport.compute(rank, (key, value) -> getNextValue(value));
}

private int getNextValue(Integer value) {
if(value == null) {
return 1;
}
return value + 1;
}

public int findReportByMatchCount(Rank rank) {
return lottoResultReport.getOrDefault(rank, 0);
}

public double calculateProfit(int gameCount) {
public double calculateProfit() {
long profit = sum();
long cost = gameCount * LottoCommonValue.DEFAULT_LOTTO_PRICE.value();
long cost = ticketCount * LottoCommonValue.DEFAULT_LOTTO_PRICE.value();
return calculateProfitRate(profit, cost);
}

Expand Down
14 changes: 6 additions & 8 deletions src/main/java/step2/domain/LottoTicket.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class LottoTicket {

Expand All @@ -29,20 +30,17 @@ public boolean isContain(LottoNo number) {
return this.lottoTicket.contains(number);
}

public Rank checkLottoTicket(LottoTicket winningTicket, int bonusNumber) {
public Rank checkLottoTicket(LottoTicket winningTicket, LottoNo bonusNumber) {
int count = (int) lottoTicket.stream()
.filter(i -> winningTicket.isContain(i))
.count();

return Rank.toPrizeMoney(count, isContain(LottoNo.of(bonusNumber)));
return Rank.rank(count, isContain(bonusNumber));
}

public String printTicket() {
StringBuilder stringBuilder = new StringBuilder();
lottoTicket.stream()
.forEach(lottoNo -> stringBuilder.append(lottoNo.number() + ", "));
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
return stringBuilder.toString();
return lottoTicket.stream()
.map(i -> String.valueOf(i.number()))
.collect(Collectors.joining(","));
Comment on lines +42 to +44
Copy link

Choose a reason for hiding this comment

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

로또 번호들을 리스트로 반환해주고 뷰에서 출력 형식에 맞게 joining을 해서 출력하는 것이 어떨까요?? 이렇게 되면 출력 형식이 바뀔 때 LottoTicket 도메인도 영향을 받을 것 같아요

}

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

import java.util.Objects;

public class Money {
private int money;

public Money(int money) {
if (money < 0) {
throw new IllegalArgumentException("잔액은 음수를 가질 수 없습니다.");
}
this.money = money;
}

public int pay(int cost) {
if (cost > this.money) {
throw new IllegalArgumentException("잔액을 초과하셨습니다.");
}
this.money -= cost;
return 0;
}
Comment on lines +15 to +21
Copy link

Choose a reason for hiding this comment

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

0이라는 반환 값이 별다른 의미가 없어보이는데 void 메서드로 만드는 것이 어떨까요?


public int balance() {
return money;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money1 = (Money) o;
return money == money1.money;
}

@Override
public int hashCode() {
return Objects.hash(money);
}
}
46 changes: 25 additions & 21 deletions src/main/java/step2/domain/Rank.java
Original file line number Diff line number Diff line change
@@ -1,41 +1,38 @@
package step2.domain;

import java.util.Arrays;
import java.util.function.BiPredicate;

public enum Rank {
MISS(0, 0, "0개 일치 (0)", (matchCount, hasBonus) -> matchCount < 3),
FIFTH(3, 5_000, "3개 일치 (5000)", (matchCount, hasBonus) -> matchCount == 3),
FOURTH(4, 50_000, "4개 일치 (50000)", (matchCount, hasBonus) -> matchCount == 4),
THIRD(5, 1_500_000, "5개 일치 (1500000)", (matchCount, hasBonus) -> matchCount == 5 && !hasBonus),
SECOND(5, 30_000_000, "5개 일치, 보너스 볼 일치(30000000원)", (matchCount, hasBonus) -> matchCount == 5 && hasBonus),
FIRST(6, 2_000_000_000, "6개 일치 (2000000000)", (matchCount, hasBonus) -> matchCount == 6);

private static final int THIRD_COUNT = 5;
MISS(0, 0, "0개 일치 (0)"),
FIFTH(3, 5_000, "3개 일치 (5000)"),
FOURTH(4, 50_000, "4개 일치 (50000)"),
THIRD(5, 1_500_000, "5개 일치 (1500000)"),
SECOND(5, 30_000_000, "5개 일치, 보너스 볼 일치(30000000원)"),
FIRST(6, 2_000_000_000, "6개 일치 (2000000000)");
Copy link

Choose a reason for hiding this comment

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

뷰와 도메인을 철저하게 분리하고자 한다면 여기의 message 부분도 뷰에 따로 빼두는 것도 좋을 것 같아요!!

만약 string 필드의 의미가 6등, 1등 등 도메인에 대한 설명이나 description에 해당하는 부분이라면 그대로 가질 수 있겠지만 현재 영역은 출력을 위한 message에 가깝기에 도메인 객체가 가지긴 적절하지 않은 필드인 것 같아요!!

예를 들어 View 패키지에 RankMessage라는 enum 객체를 두고 해당 객체가 Rank 객체외 메시지를 필드로 가지도록 하면 이러한 뷰와 도메인이 얽히는 문제를 해결하는 방법 중 하나가 될 수 있을 것 같아요!!


private int matchCount;
private long prizeMoney;
private BiPredicate<Integer, Boolean> predicate;

private String message;

Rank(int matchCount, long rank, String message, BiPredicate<Integer, Boolean> predicate) {
Rank(int matchCount, long rank, String message) {
this.matchCount = matchCount;
this.prizeMoney = rank;
this.message = message;
this.predicate = predicate;
}

public static Rank toPrizeMoney(int matchCount, boolean hasBonus) {
public static Rank rank(int matchCount, boolean matchBonus) {
if (matchCount < FIFTH.matchCount) {
return MISS;
}

return Arrays.stream(values())
.filter(prizeMoney -> prizeMoney.predicate.test(matchCount, hasBonus))
.findAny()
.orElse(Rank.MISS);
}
if (SECOND.isSameMathCount(matchCount)) {
return rankSecondOrThird(matchBonus);
}

public int matchCount() {
return this.matchCount;
return Arrays.stream(values())
.filter(rank -> rank.isSameMathCount(matchCount))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}

public long prizeMoney() {
Expand All @@ -49,4 +46,11 @@ public String message() {
private boolean isSameMathCount(int matchCount) {
return this.matchCount == matchCount;
}

private static Rank rankSecondOrThird(boolean matchBonus) {
if (matchBonus) {
return SECOND;
}
return THIRD;
}
}
Loading