Skip to content
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
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
### 프로그램 분석

---

**로또 기본 룰**

- 로또의 기본적인 규칙은 45개의 숫자 중에서 6개를 선택하는 방식입니다. 1등에 당첨되기 위해서는 6자리의 숫자가 맞으면 1등에 당첨입니다. 당첨번호 5개 + 보너스 번호 1개에 맞으면 2등이며 보너스 번호는 2등에만 해당됩니다.
1. 입력한 금액 % 1000 만큼의 로또 개수 발급
- 구입한 금액은 저장해야함. → Or 발급 개수를 저장.
- 나중에 수익률 계산할 때 사용.
2. 당첨번호를 입력 받아야함.
- 당첨 번호 + 보너스 번호
3. 발급된 각 로또 번호와 당첨번호를 매칭해서 개수 일치 카운팅
- 일치 카운트의 로또 개수 리턴 해아함.

상금

- 5등
- 3개 일치 : 5000원
- 4등
- 4개 일치 : 50000원
- 3등
- 5개 일치 : 1500000원
- 2등
- 5개 일치, 보너스 볼 일치 : 30000000원
- 1등
- 6개 일치 : 20000000000원
4. 3번 내용 바탕으로 총 수익률 계산.

### 요구사항 분석

---

- 결과 값들에 대해서는 배열 보단 `ArrayList`로 일급 컬렉션으로 관리하기.
- `JAVA ENUM`을 활용하기 → 상금을 활용하면 좋을 듯??
- 규칙 3 : 모든 원시값과 문자열을 포장한다.
- 규칙 5 : 줄여쓰지 않는다.
- 규칙 8 : 일급 컬렉션을 쓴다.
- 배열 대신 `ArrayList`로 결과 값을 일급 컬렉션으로 관리하는 것이 좋음

### 객체 설명

|클래스|역할과 책임|
|------|---|
|Money|로또 진행에 필요한 돈을 관리하는 불변 객체. <br> 티켓을 구매, 티켓 구매 가능 여부 판단.|
|LottoShop|로또 티켓을 발급해주는 객체. <br> 수동 티켓과 자동 로또 티켓 발급.|
|LottoNumberGenerator|로또 번호를 랜덤으로 발급해주는 객체.<br> 로또 상점에서 발급되는 티켓을 생성해줌.<br>입력된 숫자를 로또 번호 객체로 변환.|
|LottoNumber|로또 번호를 관리하는 객체.<br>로또번호인 1~45 사이 값만 생성할 수 있도록 강제함.|
|LottoTicket|실제 추첨에 사용될 수 있는 로또 티켓을 관리하는 객체. <br> 로또 게임에 유효한 티켓만 발급될 수 있도록 강제.<br>로또 티켓을 당첨 번호와 비교하며 당첨 결과를 알려줌.|
|WinningTicket|당첨 번호(보너스 번호 포함)를 관리하는 객체.|
|LottoTickets|실제 추첨에 사용될 수 있는 로또 티켓들을 관리하는 객체.<br>모든 티켓을 당첨 번호와 비교하며 당첨 결과를 알려줌.|
|LottoRank|당첨 순위에 대한 정보를 담고 있는 열거 객체.|
|LottoResult|최종 로또 추첨 결과를 가지고 있는 객체.|
|WinningStatistics| 추첨 결과에 따른 통계량을 구해주는 객체.|



### 힌트

---

- 로또 자동 생성은 `Collections.shuffle()` 메소드 활용
- `Collections.sort()` 메소드를 활용해 정렬 가능
- `ArrayList`의 `Contains()` 메소드를 활용하면 어떤 값이 존재하는지 유무를 판단할 수 있음


#### 로또 흐름
- 시작! 돈을 넣어라.
- 게임에 참여하는 돈을 입력 받음
- 돈을 주고, 티켓을 받음.
- 자동으로 1000당 한 개씩 반환해줌.
- 복권 번호는 1~45번까지 있고 총 6개의 번호를 반환.
- 중복번호 없어야 하고, 각 티켓은 Shuffle되서 반환되어야 함
- 당첨 결과를 알려줘!
- 당첨번호와 보너스 번호가 입력을 받음
- 당첨 번호와 보너스 번호는 중복되면 안됨.
- 발급받은 티켓에 대한 당첨 결과를 알려줘
- 각 티켓별 결과 반환
- 당첨결과에 따른 통계량을 계산해서 출력해줘!
- 당첨 결과를 계산해주고 출력
5 changes: 3 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Thu May 21 18:30:01 KST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
28 changes: 28 additions & 0 deletions src/main/java/com/javabom/lotto/LottoApplication.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
package com.javabom.lotto;

import com.javabom.lotto.domain.result.LottoResult;
import com.javabom.lotto.domain.result.WinningStatistics;
import com.javabom.lotto.domain.shop.LottoNumbersGenerator;
import com.javabom.lotto.domain.shop.LottoShop;
import com.javabom.lotto.domain.ticket.LottoTickets;
import com.javabom.lotto.domain.ticket.WinningTicket;
import com.javabom.lotto.domain.vo.Money;
import com.javabom.lotto.dto.InputDto;
import com.javabom.lotto.dto.WinningNumbersDto;
import com.javabom.lotto.dto.WinningStatisticsDto;
import com.javabom.lotto.view.InputView;
import com.javabom.lotto.view.OutputView;

public class LottoApplication {
public static void main(String[] args) {
InputDto inputDto = InputView.askInputMoneyAndManual();
LottoShop lottoShop = new LottoShop(new LottoNumbersGenerator());

LottoTickets manualTickets = lottoShop.buyTicketsByManualInLottoShop(inputDto.getManualNumbers());
Money remainMoney = lottoShop.getRemainMoney(manualTickets, inputDto.getMoney());
LottoTickets automaticTickets = lottoShop.buyTicketsByAutomaticInLottoShop(remainMoney);

OutputView.printLotteryTickets(automaticTickets, manualTickets);
WinningNumbersDto winningNumbersDto = InputView.askWinningNumbers();
WinningTicket winningTicket = new WinningTicket(winningNumbersDto.getWinningNumbers(),
winningNumbersDto.getBonusNumber());

LottoTickets allTickets = manualTickets.joinTickets(automaticTickets);
Copy link

Choose a reason for hiding this comment

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

어차피 join 할거면 처음부터 LottoShop에서 join한 로또티켓을 반환하지 않은 이유는 뭘까요? getRemainMoney가 어색하게느껴졌으나 분리한 이유가 있을것이라고 생각했는데 마지막에 합치는걸 보니까 혼란스러워용. 단지 각각의 장수를 출력하기 위해 분리한 것이라면 다른 방법은 없을까요?

Copy link
Author

Choose a reason for hiding this comment

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

저도 그래서 단순히 장수 구분을 위해 사용하기에는 어색해서 다른 방법을 고민하다 잘 떠오르지 않아서 일단 저렇게 했습니다 ㅠㅠ

LottoResult lottoResult = allTickets.getLottoResult(winningTicket);

OutputView.printWinningStatistics(new WinningStatisticsDto(new WinningStatistics(lottoResult)));
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/javabom/lotto/domain/result/LottoRank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.javabom.lotto.domain.result;

import java.util.Arrays;

public enum LottoRank {
FIFTH_PLACE(5000, 3),
FOURTH_PLACE(50000, 4),
THIRD_PLACE(1500000, 5),
SECOND_PLACE(30000000, 5),
FIRST_PLACE(2000000000, 6),
FAIL(0, 0);

private int prizeMoney;
Copy link

Choose a reason for hiding this comment

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

Money vo를 활용해보아용

private int sameCount;

LottoRank(int prizeMoney, int sameCount) {
this.prizeMoney = prizeMoney;
this.sameCount = sameCount;
}

public static LottoRank findBySameCount(int sameCount) {
Copy link

Choose a reason for hiding this comment

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

private!

return Arrays.stream(LottoRank.values())
.filter(rankInfo -> rankInfo.sameCount == sameCount)
.findAny()
Copy link

Choose a reason for hiding this comment

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

findAny를 사용하게되면 병렬로 수행 시 먼저 찾는 것을 반환한다고 하더라구요. 지금같은 경우는 순차스트림이기 때문에 먼저있는 THIRD_PLACE를 반환하는 것을 "우리는"알지만, 만약 이 이넘이 "순서에 의존한다"는것을 모르는 다른 개발자가 SECOND_PACE와 THIRD_PLACE 상수의 순서를 바꾸면 어떻게될까요? 그 사용자는 3등임에도 2등으로 출력이될거예요(ㄱㅇㄷ) 저한테 코멘트남긴 것에 대한 답변도 여기에서 나올 수 있겠네요!!

.orElse(FAIL);
}

public static LottoRank findLottoRank(int sameCount, boolean hasBonusNumber) {
if (sameCount == 5 && hasBonusNumber) {
return LottoRank.SECOND_PLACE;
}

return findBySameCount(sameCount);
}

public int getPrizeMoney() {
return prizeMoney;
}

}
27 changes: 27 additions & 0 deletions src/main/java/com/javabom/lotto/domain/result/LottoResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.javabom.lotto.domain.result;

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

public class LottoResult {

private final List<LottoRank> results;

public LottoResult(List<LottoRank> results) {
this.results = results;
}

public int getRankCount(LottoRank rank) {
return (int) results.stream()
.filter(lottoRank -> lottoRank.equals(rank))
.count();
}

public List<LottoRank> getResults() {
return Collections.unmodifiableList(results);
}

public int size() {
return results.size();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.javabom.lotto.domain.result;

import com.javabom.lotto.domain.shop.LottoShop;

public class WinningStatistics {

private final LottoResult results;

public WinningStatistics(LottoResult results) {
this.results = results;
}

private long calculateRevenue() {
long revenue = 0;
for (LottoRank result : results.getResults()) {
Copy link

Choose a reason for hiding this comment

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

LottoResult의 값을 꺼내지 않고 메시지를 전달하는 방식은 어떨까요? getter를 최대한 지양해보는 것도 좋을 것 같아요. 그리고 이 메서드는 calculateProfitRatio 아래에 위치 하는 것이 더 좋을 듯 합니다.

revenue += result.getPrizeMoney();
}
return revenue;
}

public int calculateProfitRatio() {
long revenue = calculateRevenue();
return (int) ((revenue / (double) calculateCostMoney()) * 100);
}

private long calculateCostMoney() {
return results.size() * LottoShop.TICKET_PRICE.getValue();
}
Comment on lines +21 to +28
Copy link
Member

Choose a reason for hiding this comment

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

WinningStatistics니까 생성자에서 아예 수익률을 받는건 어떨가 생각해요. 그러면 LottoShop에 대한 쓸데없는 의존성을 없앨 수 있다고 생각해요. (상수접근을 위해만 의존성이 필요하니)
그리고 get() 메소드는 최대한 지양하는 방향으로 코드를 작성해보는것도 좋아보여요

Copy link
Author

Choose a reason for hiding this comment

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

그렇게 되면, WinningStatistics객체 존재 자체가 의미가 크게 없어서요!

현재는 수익률만 계산하지만, 추후에 로또 추첨 결과로 다양한 통계값을 낼 수도 있는 객체가 필요하다고 봐서 따로 빼놓은 객체라서요! (수익률을 생성자로 받아버리면 Real 깡통객체네용)

get 메소드는 지향하는 방향으로 수정해보겠습니다! 감사함돠


public LottoResult getResults() {
return results;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.javabom.lotto.domain.shop;

import com.javabom.lotto.domain.ticket.LottoNumber;
import com.javabom.lotto.domain.ticket.LottoTicket;

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

public class LottoNumbersGenerator {

private final List<LottoNumber> lottoNum;

public LottoNumbersGenerator() {
lottoNum = IntStream.rangeClosed(LottoNumber.NUMBER_BEGIN, LottoNumber.NUMBER_END)
.mapToObj(LottoNumber::new)
.collect(Collectors.toList());
}

public List<LottoNumber> generate() {
Collections.shuffle(lottoNum);
return new ArrayList<LottoNumber>(lottoNum.subList(0, LottoTicket.LOTTO_NUMBER_COUNT)) {
};
}
Comment on lines +20 to +24
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public List<LottoNumber> generate() {
Collections.shuffle(lottoNum);
return new ArrayList<LottoNumber>(lottoNum.subList(0, LottoTicket.LOTTO_NUMBER_COUNT)) {
};
}
public List<LottoNumber> generate() {
Collections.shuffle(lottoNum);
return new ArrayList<>(lottoNum.subList(0, LottoTicket.LOTTO_NUMBER_COUNT));
}

<> 생략이 가능하구 {}는 필요없습니다.

Copy link
Member

Choose a reason for hiding this comment

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

또, 랜덤 관련해서 테스트가 불가능해보입니다!


public static List<LottoNumber> convertToLottoNumbers(List<Integer> numbers) {
return numbers.stream()
.map(LottoNumber::new)
.collect(Collectors.toList());
}
}
57 changes: 57 additions & 0 deletions src/main/java/com/javabom/lotto/domain/shop/LottoShop.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.javabom.lotto.domain.shop;

import com.javabom.lotto.domain.ticket.LottoNumber;
import com.javabom.lotto.domain.ticket.LottoTicket;
import com.javabom.lotto.domain.ticket.LottoTickets;
import com.javabom.lotto.domain.vo.Money;

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


public class LottoShop {

public final static Money TICKET_PRICE = new Money(1000);
private final LottoNumbersGenerator numberGenerator;

public LottoShop(LottoNumbersGenerator generator) {
this.numberGenerator = generator;
}


public LottoTickets buyTicketsByAutomaticInLottoShop(Money money) {
List<LottoTicket> lottoTickets = new ArrayList<>();
Money currentMoney = money;

while (currentMoney.canSpendMoney(TICKET_PRICE)) {
lottoTickets.add(generateTicket());
currentMoney = currentMoney.spendMoney(TICKET_PRICE);
}

return new LottoTickets(lottoTickets);
}

private LottoTicket generateTicket() {
return new LottoTicket(numberGenerator.generate());
}

public LottoTickets buyTicketsByManualInLottoShop(List<List<LottoNumber>> manualLottoNumbers) {
Copy link

Choose a reason for hiding this comment

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

수동로또는 티켓이 아닌것 같아서 LottoTickets 라는 일급컬렉션을 활용하지 않은걸까요? 이런 구조야말로 일급컬렉션이 필요한 구조라고 생각해요

List<LottoTicket> manualTickets = new ArrayList<>();

for (List<LottoNumber> lottoNumber : manualLottoNumbers) {
manualTickets.add(giveLottoTicket(lottoNumber));
}

return new LottoTickets(manualTickets);
}

private LottoTicket giveLottoTicket(List<LottoNumber> lottoNumbers) {
return new LottoTicket(lottoNumbers);
}
Comment on lines +48 to +50
Copy link
Member

Choose a reason for hiding this comment

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

이 메소드는 불필요해보여요. 이 메소드를 쓰는곳에서 new LottoTicket(~~~) 을 부르는게 훨씬 나아보입니다.


public Money getRemainMoney(LottoTickets lottoTickets, Money money) {
int price = lottoTickets.size() * TICKET_PRICE.getValue();

return money.spendMoney(new Money(price));
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/javabom/lotto/domain/ticket/LottoNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.javabom.lotto.domain.ticket;

import java.util.Objects;

public class LottoNumber {

private final int number;
public final static int NUMBER_BEGIN = 1;
public final static int NUMBER_END = 45;


public LottoNumber(int number) {
this.number = number;
validLottoNumber();
}

public int getNumber() {
return this.number;
}

private void validLottoNumber() {
if (number < NUMBER_BEGIN || number > NUMBER_END) {
throw new IllegalArgumentException(String.format("로또 번호가 아닙니다. - %s", number));
}

}

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

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