Skip to content

Commit

Permalink
[1단계 - 로또 구현] 검프(김태정) 미션 제출합니다. (#269)
Browse files Browse the repository at this point in the history
* refactor: remove spark library, html

* docs: 기능 요구사항 정리

구현할 기능을 README로 정리하였음

* docs: 객체 메세지 정의

협력에 필요한 메세지를 먼저 정의 하였음.

* docs: 객체 협력 정리

이미지를 통해 객체 협력을 정리하였다

* feat: 보장된 로또 번호로 한 로또를 생성한다

중복 검사, 번호 검사, 갯수 검사 완료

* feat: 보장된 구입금액을 입력 받는다.

음수, 실수가 들어모면 에러가 발생한다.

* feat: 구입금액 따른 로또의 갯수를 구한다.

* feat: 보장된 당첨 번호를 입력한다.

보장된 당첨 번호(중복되지 않은 1~45의 숫자, 6개의 숫자)
보장되지 않은 당첨번호 에러가 발생한다.

* feat: LottoNumber 객체 생성

로또 번호를 가질 LottoNumber 객체 생성 및 검증 완료

* feat: LottoNumbers 객체 생성

LottoNumber을 관리할 LottoNumbers 객체를 생성 및 검증하였음

* refactor: Lotto 및 WinningLotto의 인스턴스 변수 변경

 private final List<Integer> lottoNumbers;
-> private final LottoNumbers lottoNumbers;

* feat: 보장된 보너스 볼을 입력 받는다

BonusNumber 객체 생성 및 검증 완료

* refactor: BonusNumber 정적 팩토리 메서드 생성

생성자를 private으로 감추고, 정적 팩토리 메서드를 생성하였음

* feat: 구현 부분 구입금액 입력 확인

구현 부분에서 구입금액을 입력 후 저장하였음

* feat: 랜덤 로또 번호 생성

RandomLottoUtil 객체 생성

* feat: 구입금액 입력 화면 출력 로직 생성

* refactor: Budget 메소드 추가

int value를 반환하는 Budget메소드를 추가하였음.

* feat: 구매 갯수 출력 로직 작성

* feat: 로또 갯수만큼 보장된 로또번호를 출력한다.

dto를 통해 로또 번호 출력

* refactor: LottoGameMachine::gameStart() 내부 구현 변경

LottoGameMachine의 책임을 Screen에 옮김

* feat: 임시 저장

* feat: 콘솔 창에 당첨번호를 입력한다.

* feat: 로또 당첨 확인 로직 구현

* feat: 보너스 볼 포함 당첨 현황을 구한다

* feat: 당첨 통계 출력

* feat: 수익률 계산 로직

* refactor: naming 변경

Number -> ball 로 변경하였음

* refactor: dto naming 변경

dto의 이름을 변경하였음

* refactor: BonusBall 객체 제거

분리될 만큼의 책임을 가지지 않기 때문에 제거함.

* refactor: WinningLotto, LottoBalls 책임 변경

- [x] LottoBalls 생성자 책임 정의 - > LottoBalls 생성자 제거
- [x] 입력 파싱 책임 제거
- [x] gnerate 제거
- [x] of 제거
- [x] winngLotto 생성 로직 변경

* refactor: 클래스 네이밍 변경

Lotto -> LottoTicket
Lottos -> LottoTickets
LottoCount -> TicketCount

* refactor: Budget 클래스 BettingMoney로 변경

Budget의 책임이 모호하여 BettingMoney로 변경함.

* refactor: DTO 제거 및 책임 분리

LottoService를 추가하여 LottoGameMachine의 책임을 이동하였고,
DTO를 제거하면서 필요한 로직을 수정함.
result 클래스 변경 및 테스트를 추가하였음

* refactor: LottoRank 클래스 수정

- [x] 사용되지 않는 값 제거
- [x] 메서드 네이밍 변경

* refactor: 에러 출력메세지 상수 활용

* refactor: LottoGameScreen 변경

출력 로직 변경 및 변경으로 인한 LottoRank 변경

* refactor: test 한글 네이밍 영어로 변경

모든 테스트의 네이밍을 변경하였음

* refactor: code convention

의미없는 띄어쓰기 삭제 맟, 메소드 선언 순서 변경

* docs: 리팩토링 목록 정리

리팩토링한 목록을 정리하였음

Co-authored-by: 포비 <pobi@woowahan.com>
  • Loading branch information
Livenow14 and pobiconan authored Feb 22, 2021
1 parent e984845 commit 36b0112
Show file tree
Hide file tree
Showing 32 changed files with 1,119 additions and 36 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# java-lotto
# java-lottoTicket
로또 미션 진행을 위한 저장소
3 changes: 0 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ repositories {
}

dependencies {
compile('com.sparkjava:spark-core:2.9.0')
compile('com.sparkjava:spark-template-handlebars:2.7.1')
compile('ch.qos.logback:logback-classic:1.2.3')
testCompile('org.junit.jupiter:junit-jupiter:5.6.0')
testCompile('org.assertj:assertj-core:3.15.0')
}
Expand Down
70 changes: 70 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
## 객체 협력 정리
![객체 협력 정리](../image/객체협력정리.png)


## 매세지 정의
- 로또를 구매하라
- 로또를 생성하라
- 로또 갯수만큼 로또 번호를 생성하라
- 로또 번호를 생성하라
- 한 게임을 생성하라
- 구입금액을 입력하라
- 당첨 번호를 입력하라
- 보너스 볼을 입력하라
- 로또 갯수를 구하라
- 생성된 로또번호를 출력하라
- 당첨 갯수를 구하라
- 수익률을 계산하라

## 기능 요구사항 정리
- [x] 구현 부분 구입금액 입력 확인
- [x] 보장된 구입금액을 입력 받는다.
- 보장된 구입금액(로또 티켓 구입 금액보다 큰 양수의 숫자)
- [x] 보장된 구입금액이 아니면 에러가 발생한다.
- `IllegalArgumentException`
- [x] 구입금액 따른 로또의 갯수를 구한다.
- [x] 보장된 로또 번호로 한 로또를 생성한다.
- 보장된 로또 번호(중복되지 않은 1~45의 숫자이며, 6개의 숫자)
- [x] 로또 갯수만큼 보장된 로또번호를 출력한다.
- 보장된 로또번호(중복되지 않은 1~45의 숫자이며, 6개의 숫자)
- [x] 콘솔 창에 당첨번호를 입력한다.
- [x] 보장된 당첨 번호를 입력한다.
- 보장된 당첨 번호(중복되지 않은 1~45의 숫자, 6개의 숫자)
- [x] 보장된 당첨 번호가 아니면 에러가 발생한다.
- `IllegalArgumentException`
- [x] 보장된 보너스 볼을 입력 받는다.
- 보장된 보너스 볼(보장된 당첨 번호와 중복되지 않는 1~45의 숫자)
- [x] 보장된 보너스 볼이 아니면 에러가 발생한다.
- `IllegalArgumentException`
- [x] 보너스 볼 포함 당첨 현황을 구한다.
- [x] 당첨 통계를 출력한다.
- [x] 수익률을 출력한다.

## 리펙토링 목록 정리
- [x] DTO 제거기(DTO 없이 view에 값을 전달 할 수 있다.)
- [x] 책임에 대해 한번더 생각하기
- [x] BonusBall 제거
- [x] LottoGameMachine 책임 분리하기
- [x] 입력 파싱 책임 제거
- [x] gnerate 제거
- [x] of 제거
- [x] 테스트메서드 한글 이름 영어로 변경 (의도를 더 드러내자)
- [x] 사용되지 않는 값 제거
- [x] 사용되지 않은 메서드 제거
- [x] 제네릭 빠진 것 확인
- [x] DisplayName 의도를 더 정확히 드러내


## 프로그래밍 요구사항
- indent(인덴트, 들여쓰기) depth를 2단계에서 1단계로 줄여라.
- depth의 경우 if문을 사용하는 경우 1단계의 depth가 증가한다. if문 안에 while문을 사용한다면 depth가 2단계가 된다.
- else를 사용하지 마라.
- 메소드의 크기가 최대 10라인을 넘지 않도록 구현한다.
- method가 한 가지 일만 하도록 최대한 작게 만들어라.
- 배열 대신 ArrayList를 사용한다.
- java enum을 적용해 프로그래밍을 구현한다.
- 규칙 3: 모든 원시값과 문자열을 포장한다.
- 규칙 5: 줄여쓰지 않는다(축약 금지).
- 규칙 8: 일급 콜렉션을 쓴다.


Binary file added image/객체협력정리.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import domain.LottoGameMachine;
import service.LottoService;
import view.LottoGameScreen;
import view.MainScreen;

public class Application {
public static void main(String[] args) {
LottoGameMachine lottoGameMachine = new LottoGameMachine();
LottoService lottoService = new LottoService(lottoGameMachine);
GameManageApplication gameManageApplication = new GameManageApplication(new MainScreen(), new LottoGameScreen(), lottoService);
gameManageApplication.run();
}
}
56 changes: 56 additions & 0 deletions src/main/java/GameManageApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import domain.bettingMoney.BettingMoney;
import domain.lotto.LottoTickets;
import domain.lotto.TicketCount;
import domain.lotto.WinningLotto;
import domain.result.Result;
import service.LottoService;
import util.InputUtil;
import view.LottoGameScreen;
import view.MainScreen;
import view.dto.LottoGameResultDto;

import java.util.Set;

public class GameManageApplication {
private final MainScreen mainScreen;
private final LottoGameScreen lottoGameScreen;
private final LottoService lottoService;

public GameManageApplication(final MainScreen mainScreen, final LottoGameScreen lottoGameScreen, LottoService lottoService) {
this.mainScreen = mainScreen;
this.lottoGameScreen = lottoGameScreen;
this.lottoService = lottoService;
}

public void run() {
BettingMoney bettingMoney = getBettingMoney();
TicketCount ticketCount = getTicketCount(bettingMoney);
mainScreen.showTicketCount(ticketCount);
LottoTickets lottoTickets = lottoService.getLottoTickets(bettingMoney);
lottoGameScreen.showAllLottoStatus(lottoTickets.getLottoTickets());
WinningLotto winningLotto = getWinningLotto();

Result result = new Result(lottoTickets, winningLotto);
lottoGameScreen.showGameResult(new LottoGameResultDto(result.getResults()));
lottoGameScreen.showRevenueResult(result.findEarningsRate(bettingMoney));
}

private WinningLotto getWinningLotto() {
lottoGameScreen.confirmWinningLotto();
Set<Integer> winningNumbers = InputUtil.inputWinningNumbers();
lottoGameScreen.confirmBonusLotto();
int bonusNumber = InputUtil.inputBonusNumber();
return new WinningLotto(winningNumbers, bonusNumber);
}

private TicketCount getTicketCount(final BettingMoney bettingMoney) {
int ticketCount = bettingMoney.getTicketCount();
return TicketCount.of(ticketCount);
}

private BettingMoney getBettingMoney() {
mainScreen.showInputMoney();
int input = InputUtil.nextInt();
return BettingMoney.of(input);
}
}
24 changes: 24 additions & 0 deletions src/main/java/domain/LottoGameMachine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package domain;

import domain.ball.LottoBall;
import domain.ball.LottoBalls;
import domain.bettingMoney.BettingMoney;
import domain.lotto.LottoTicket;

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

public class LottoGameMachine {
public List<LottoTicket> buyTickets(BettingMoney bettingMoney) {
int ticketCount = bettingMoney.getTicketCount();
return IntStream.range(0, ticketCount)
.mapToObj(count -> makeTicket())
.collect(Collectors.toList());
}

private LottoTicket makeTicket() {
List<LottoBall> lottoBalls = LottoBalls.getRandomLottoBalls();
return new LottoTicket(new LottoBalls(lottoBalls));
}
}
56 changes: 56 additions & 0 deletions src/main/java/domain/ball/LottoBall.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package domain.ball;

import java.util.Objects;

public class LottoBall implements Comparable<LottoBall> {
public static final int MIN_LOTTO_VALUE = 1;
public static final int MAX_LOTTO_VALUE = 45;
public static final String PERMIT_LOTTO_NUMBER_EXCEPTION_MESSAGE = "%d~%d 사이의 번호만 허용합니다.";

private final int value;

public LottoBall(final int value) {
validateNumber(value);
this.value = value;
}

private void validateNumber(final int value) {
if (!isBetweenNumber(value)) {
throw new IllegalArgumentException(String.format(PERMIT_LOTTO_NUMBER_EXCEPTION_MESSAGE, MIN_LOTTO_VALUE, MAX_LOTTO_VALUE));
}
}

private boolean isBetweenNumber(final int number) {
return number >= MIN_LOTTO_VALUE && number <= MAX_LOTTO_VALUE;
}

public int getValue() {
return value;
}

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

@Override
public int hashCode() {
return Objects.hash(value);
}

@Override
public int compareTo(LottoBall o) {
if (this.value > o.value) {
return 1;
}
return -1;
}

@Override
public String toString() {
return value + " ";
}
}
75 changes: 75 additions & 0 deletions src/main/java/domain/ball/LottoBalls.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package domain.ball;

import domain.result.LottoRank;

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

import static domain.ball.LottoBall.MAX_LOTTO_VALUE;
import static domain.ball.LottoBall.MIN_LOTTO_VALUE;

public class LottoBalls {
private static final int LOTTO_BALL_SIZE = 6;

private final List<LottoBall> lottoBalls;

public LottoBalls(final List<LottoBall> lottoBalls) {
List<LottoBall> copy = new ArrayList<>(lottoBalls);
validateLottoNumbers(copy);
this.lottoBalls = copy;
}

public static List<LottoBall> getRandomLottoBalls() {
List<LottoBall> lottoBalls = IntStream.rangeClosed(MIN_LOTTO_VALUE, MAX_LOTTO_VALUE)
.mapToObj(LottoBall::new)
.collect(Collectors.toList());
Collections.shuffle(lottoBalls);
return lottoBalls.stream()
.limit(LOTTO_BALL_SIZE)
.sorted()
.collect(Collectors.toList());
}

public List<LottoBall> getLottoBalls() {
List<LottoBall> copy = new ArrayList<>(this.lottoBalls);
return Collections.unmodifiableList(copy);
}

public LottoRank matchCount(LottoBalls lottoBalls, LottoBall bonusBall) {
int count = (int) this.lottoBalls.stream()
.filter(lottoBalls::contains)
.count();

boolean containBonus = this.lottoBalls.stream()
.anyMatch(bonusBall::equals);

return LottoRank.findRankByBonusAndMatches(containBonus, count);
}

private void validateLottoNumbers(final List<LottoBall> lottoBalls) {
validateDuplicate(lottoBalls);
validateSize(lottoBalls);
}

private void validateDuplicate(final List<LottoBall> lottoBalls) {
boolean isUnique = lottoBalls.stream()
.allMatch(new HashSet<>()::add);
if (!isUnique) {
throw new IllegalArgumentException(String.format("로또 번호에 중복된 값이 있습니다. 다시 입력해주세요. 입력값 %s", lottoBalls.toString()));
}
}

private void validateSize(final List<LottoBall> lottoNumbers) {
if (lottoNumbers.size() != LOTTO_BALL_SIZE) {
throw new IllegalArgumentException(String.format("%d개의 로또 번호가 필요합니다.", LOTTO_BALL_SIZE));
}
}

private boolean contains(LottoBall lottoBall) {
return this.lottoBalls.contains(lottoBall);
}
}
36 changes: 36 additions & 0 deletions src/main/java/domain/bettingMoney/BettingMoney.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package domain.bettingMoney;

import java.math.BigDecimal;

import static domain.lotto.LottoTicket.TICKET_PRICE;

public class BettingMoney {
private static final String NOT_ENOUGH_MONEY_EXCEPTION_MESSAGE = "%d 이상의 금액만 가능합니다. 현재 입력 금액: %d";

private final int bettingMoney;

private BettingMoney(final int bettingMoney) {
validateMoney(bettingMoney);
this.bettingMoney = bettingMoney;
}

public static BettingMoney of(final int bettingMoney) {
return new BettingMoney(bettingMoney);
}

public int getTicketCount() {
return this.bettingMoney / TICKET_PRICE;
}

public BigDecimal getEarningRate(final int prizeMoney) {
BigDecimal bettingMoney = BigDecimal.valueOf(this.bettingMoney);
BigDecimal prize = BigDecimal.valueOf(prizeMoney);
return prize.divide(bettingMoney);
}

private void validateMoney(final int bettingMoney) {
if (bettingMoney < TICKET_PRICE) {
throw new IllegalArgumentException(String.format(NOT_ENOUGH_MONEY_EXCEPTION_MESSAGE, TICKET_PRICE, bettingMoney));
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/domain/lotto/LottoTicket.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package domain.lotto;

import domain.ball.LottoBall;
import domain.ball.LottoBalls;
import domain.result.LottoRank;

import java.util.Collections;
import java.util.List;

public class LottoTicket {
public static final int TICKET_PRICE = 1000;

private final LottoBalls lottoBalls;

public LottoTicket(final LottoBalls lottoBalls) {
this.lottoBalls = lottoBalls;
}

public List<LottoBall> getLottoBalls() {
List<LottoBall> lottoBalls = this.lottoBalls.getLottoBalls();
return Collections.unmodifiableList(lottoBalls);
}

public LottoRank findMatchesNumber(WinningLotto winningLotto) {
return winningLotto.winningMatchCount(lottoBalls);
}
}
Loading

0 comments on commit 36b0112

Please sign in to comment.