-
Notifications
You must be signed in to change notification settings - Fork 330
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[1단계 - 로또 구현] 케빈(박진홍) 미션 제출합니다. (#228)
* refactor: application 수정 * feat: 요구사항 정리 * feat: 로또 티켓 구매 금액 생성 및 유효성 로직 기능 구현 * feat: 입력 금액에서 구매 가능한 로또 티켓 개수 계산 기능 구현 * feat: 로또 번호 생성 및 유효성 검증 로직 추가 * test: 로또 번호 정상 생성 테스트 추가 * feat: 로ë� 티켓은 중복되지 않은 6자리의 수로 구성되어야 한다 * feat: 로또 티켓 정상 발급 테스트 및 기능구현 * refactor: LottoTicket 검ìì�성 검증 로직 메서드 분리 * feat: 로또 번호 자동 생성하는 기능 구현 * feat: LottoTickets 생성 및 테스트 코드 추가 * feat: 당첨 번호 생성 및 보너스 볼 중복 검사 기능 구현 * refactor: WinningLottoTicket 클래스를 상속보다는 조합을 사용 * feat: 로또 순위 반환 기능 구현 * feat: 티켓 두장을 비교하여 맞은 개수를 반환하는 기능 구현 * refactor: contains 메서드 접근자를 퍼블릭으로 수정 * feat: LottoTickets에서 당첨 티켓을 비교하여 결과를 집계 * feat: 로또 통계는 순위별 당첨 티켓 개수를 반환한다. * refactor: 로또 티켓이 일급 컬렉션 통계 객체를 반환하도록 함 * feat: 수익률 집계 기능 구현 * feat: view 구현 * refactor: 생성자 검증 로직 메서드 추출 * refactor: LottoRank 내 of 메소드 로직 분리 * refactor: LottoStatistics 상수 추가 및 메소드명 수정 * refactor: compare 메서드명을 직관적으로 수정 * feat: PurchasingPrice 구매가능 티켓 개수 반환 메서드명 변경 * refactor: OutputView 반복문 삭제 * style: test method given when then 패턴 형식에 맞게 수정 * style: 가독성 위해 줄바꿈 추가 * refactor: LottoNumber를 싱글톤으로 변경 * refactor: LottoTicket 메서드명 수정 및 람다 메서드 참조 사용 * refactor: WinningLottoTicket 유효성 검증 로직 분리 * refactor: LottoTickets 람다 스트림 사용 및 테스트를 위한 메서드 삭제 프로덕션에서 사용하지 않는 코드는 삭제한다. * chore: 코드 컨벤션 포매팅 * refactor: getter 메서드명 변경 필드변수명 변경에 따라 getter 또한 변경한다 * refactor: 로또 번호 생성기 클래스 상수 분리 * refactor: 통계 결과 메서드 명을 getCountsByRank로 수정 등수에 따른 개수라는 의미를 직관적으로 살리기 위해 변경한다 * refactor: 수익률 계산 기능 메서드 분리 및 반복문 삭제 * refactor: LottoTickets 자동 발행 메서드명 변경 자동으로 티켓을 발권한다는 역할을 메서드명에 드러나도록 변경 * refactor: LottoRank 열거 상수 네이밍 변경 - 각 상수들의 접두로 PRIZE를 추가한다. - 상금 상수의 가독성을 위해 언더바를 추가한다. * refactor: 로또 당첨 통계 상금별 티켓 개수 반환 메서드명 변경 counts가 아닌 ticketCounts라고 명시한다. * refactor: OutputView 반복문 삭제 및 메서드 명 상세 설명 - 각 메서드가 어떤 내용을 출력하는지를 명시해준다. - 또한 반복문을 삭제하고 람다 스트림을 이용한다. * refactor: enumMap factory 추가 * refactor: 객체 생성시 기존 값들을 새로운 컬렉션에 담기도록 변경 * refactor: 인터페이스 및 열거형 접근제어자 변경 - 열거형 생성자는 기본 default를 사용한다. - 인터페이스 추상 메서드의 접근 제어자의 public은 생략한다. * refactor: LottoStatistics 클래스명을 LottoResult로 변경 - getStatistics 등의 메서드 명 또한 checkResult로 직관되게 변경 * refactor: PurchasingPrice 객체는 티켓 가격을 모르도록 변경 - 구입가격 객체는 양의 정수만을 허용하도록 변경한다. * refactor: 자동 발권 기능의 파라미터를 int가 아닌 PurchasingPrice 파라미터로 변경 * feat: LottoTickets 생성시 구매 금액이 부족한 경우 예외 발생 * feat: 로또 티켓 갯수는 LottoTickets에서 반환한다 * feat: LottoTickets가 자신을 구입하는데 든 비용을 반환하도록 함 * refactor: 수익률 집계 메서드 시그니쳐 변경 * refactor: OutputView에서 수익률 출력 방식 변경 도메인 객체로 메시지를 보내던 기존 방식을 외부에서 주입받은 수익률을 바로 출력하도록 변경한다. * feat: 로또 결과 통계 Map 초기화 및 반환 기능 구현 * refactor: LottoTicket 내부 컬렉션을 list에서 set으로 변경 * refactor: 로또 티켓 번호 정렬 기능을 View로 변경 LottoTicket은 Set이기 때문에 정렬을 View단으로 넘긴다 * refactor: getMatchCounts 메서드명 동사를 check로 변경 * refacotr: 통계 Map 초기화 메서드명 변경 Co-authored-by: DESKTOP-VDP6SAT\xntm3 <ohjoohyung@github.com>
- Loading branch information
1 parent
e984845
commit 918f6f8
Showing
21 changed files
with
865 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package lotto; | ||
|
||
import lotto.domain.*; | ||
import lotto.view.InputView; | ||
import lotto.view.OutputView; | ||
|
||
import java.util.List; | ||
|
||
public class Application { | ||
public static void main(String[] args) { | ||
PurchasingPrice purchasingPrice = new PurchasingPrice(InputView.inputPurchasingPrice()); | ||
LottoTickets lottoTickets = LottoTickets.generateAutomatic(purchasingPrice, new RandomLottoNumberGenerator()); | ||
OutputView.printPurchasedLottoTicketCounts(lottoTickets.getTicketCounts()); | ||
OutputView.printAllLottoTicketNumbers(lottoTickets); | ||
|
||
List<Integer> winningTicketNumbers = InputView.inputWinningTicketNumbers(); | ||
int bonusBallNumber = InputView.inputBonusBallNumber(); | ||
WinningLottoTicket winningLottoTicket = WinningLottoTicket.of(winningTicketNumbers, bonusBallNumber); | ||
LottoResult lottoResult = lottoTickets.checkResult(winningLottoTicket); | ||
double yield = lottoResult.calculateYield(lottoTickets.getPurchasingPrice()); | ||
OutputView.printLottoResult(lottoResult, yield); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
## 구현 기능 목록 정리 | ||
|
||
* 입력한 구입 금액은 최소 1000원 이상이어야 한다. | ||
* 예외) | ||
* 숫자가 아닌 문자열이 입력된 경우. | ||
* 1000원 미만인 경우. | ||
* 구입 금액으로 살 수 있는 로또 티켓 개수를 계산한다. | ||
* 로또 번호는 1~45번 까지만 유효하다. | ||
* 로또 티켓은 6개의 로또 번호로 구성되며, 중복이 있어서는 안 된다. | ||
* 자동(랜덤)으로 로또 티켓을 만든다. | ||
* 당첨 번호 또한 로또 티켓과 같은 규칙을 적용받는다. | ||
* 보너스볼 번호는 로또 번호와 같은 규칙을 적용받으며 당첨 번호와 중복될 수 없다. | ||
* 예외) | ||
* 당첨 번호와 중복되는 경우. | ||
* 1~45 범위의 숫자가 아닌 경우. | ||
* 구매한 로또 티켓의 번호와 당첨 번호(+ 보너스볼)를 비교한다. | ||
* 6개가 다 맞으면 1등(2000000000). | ||
* 5개 맞고 보너스 볼 일치하면 2등(30000000). | ||
* 5개 맞으면 3등(1500000). | ||
* 4개 맞으면 4등(50000). | ||
* 3개 맞으면 5등(5000). | ||
* 나머지는 상금 없음. | ||
* 맞은 개수에 따라서 로또 순위를 반환한다. | ||
* 로또 당첨 통계를 확인한다. | ||
* 각 등수별 당첨 티켓의 개수를 카운팅한다. | ||
* 전체 구매 금액 대비 수익금액의 비율(수익률)을 계산한다. | ||
* (추가) | ||
* 로또 번호 싱글톤 적용 | ||
* (가능하다면) 반복문을 스트림으로 바꿔보자 | ||
|
||
## 프로그래밍 요구 사항 | ||
|
||
* indent(인덴트, 들여쓰기) depth를 2단계에서 1단계로 줄여라. | ||
* depth의 경우 if문을 사용하는 경우 1단계의 depth가 증가한다. if문 안에 while문을 사용한다면 depth가 2단계가 된다. | ||
* else를 사용하지 마라. | ||
* 메소드의 크기가 최대 10라인을 넘지 않도록 구현한다. | ||
* method가 한 가지 일만 하도록 최대한 작게 만들어라. | ||
* 배열 대신 ArrayList를 사용한다. | ||
* java enum을 적용해 프로그래밍을 구현한다. | ||
* 규칙 3: 모든 원시값과 문자열을 포장한다. | ||
* 규칙 5: 줄여쓰지 않는다(축약 금지). | ||
* 규칙 8: 일급 콜렉션을 쓴다. | ||
|
||
## Model | ||
|
||
* PurchasingPrice | ||
* LottoNumber | ||
* LottoTicket | ||
* 당첨번호 //추후 수정 | ||
* LottoTickets | ||
* LottoResult(Statistics) //추후 수정 | ||
|
||
## View | ||
|
||
* 구입 금액을 입력받는다. | ||
* 당첨 번호를 입력받는다. | ||
* 보너스 볼을 입력받는다. | ||
* 구입한 로또 티켓 갯수를 출력한다. | ||
* 구입한 로또 티켓 번호들을 출력한다. | ||
* 당첨 통계를 출력한다. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package lotto.domain; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class LottoNumber { | ||
private static final Map<Integer, LottoNumber> CACHE = new HashMap<>(); | ||
private static final int MINIMUM_NUMBER = 1; | ||
private static final int MAXIMUM_NUMBER = 45; | ||
private static final String NUMBER_RANGE_ERROR = "로또 번호 범위가 벗어났습니다."; | ||
|
||
private final int number; | ||
|
||
private LottoNumber(int number) { | ||
validateNumber(number); | ||
this.number = number; | ||
} | ||
|
||
public static LottoNumber from(int number) { | ||
return CACHE.computeIfAbsent(number, LottoNumber::new); | ||
} | ||
|
||
private void validateNumber(int lottoNumber) { | ||
if (lottoNumber < MINIMUM_NUMBER || lottoNumber > MAXIMUM_NUMBER) { | ||
throw new IllegalArgumentException(NUMBER_RANGE_ERROR); | ||
} | ||
} | ||
|
||
public int getNumber() { | ||
return number; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package lotto.domain; | ||
|
||
import java.util.List; | ||
|
||
public interface LottoNumberGenerator { | ||
List<Integer> generate(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package lotto.domain; | ||
|
||
import java.util.Arrays; | ||
|
||
public enum LottoRank { | ||
FIRST_PRIZE(6, false, 2_000_000_000), | ||
SECOND_PRIZE(5, true, 30_000_000), | ||
THIRD_PRIZE(5, false, 1_500_000), | ||
FOURTH_PRIZE(4, false, 50_000), | ||
FIFTH_PRIZE(3, false, 5_000), | ||
MISS(0, false, 0); | ||
|
||
private final int matchCounts; | ||
private final boolean isBonusBall; | ||
private final int prizeMoney; | ||
|
||
LottoRank(int matchCounts, boolean isBonusBall, int prizeMoney) { | ||
this.matchCounts = matchCounts; | ||
this.isBonusBall = isBonusBall; | ||
this.prizeMoney = prizeMoney; | ||
} | ||
|
||
public static LottoRank of(int matchCounts, boolean isBonusBall) { | ||
if (isSecondPrize(matchCounts, isBonusBall)) { | ||
return SECOND_PRIZE; | ||
} | ||
return Arrays.stream(LottoRank.values()) | ||
.filter(lottoRank -> lottoRank != LottoRank.SECOND_PRIZE) | ||
.filter(lottoRank -> lottoRank.matches(matchCounts)) | ||
.findFirst() | ||
.orElseGet(() -> MISS); | ||
} | ||
|
||
private static boolean isSecondPrize(int matchCounts, boolean isBonusBall) { | ||
return isBonusBall == SECOND_PRIZE.isBonusBall && matchCounts == SECOND_PRIZE.matchCounts; | ||
} | ||
|
||
private boolean matches(int matchCounts) { | ||
return this.matchCounts == matchCounts; | ||
} | ||
|
||
public int getPrizeMoney() { | ||
return prizeMoney; | ||
} | ||
|
||
public int getMatchCounts() { | ||
return matchCounts; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package lotto.domain; | ||
|
||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.EnumMap; | ||
import java.util.Map; | ||
|
||
public class LottoResult { | ||
private static final long ZERO = 0; | ||
|
||
private final Map<LottoRank, Long> statistics; | ||
|
||
public LottoResult(Map<LottoRank, Long> statistics) { | ||
initiateDefaultEntry(statistics); | ||
this.statistics = new EnumMap<>(statistics); | ||
} | ||
|
||
private void initiateDefaultEntry(Map<LottoRank, Long> statistics) { | ||
Arrays.stream(LottoRank.values()) | ||
.forEach(lottoRank -> statistics.computeIfAbsent(lottoRank, key -> ZERO)); | ||
} | ||
|
||
public long getTicketCountsByRank(LottoRank lottoRank) { | ||
return statistics.computeIfAbsent(lottoRank, key -> ZERO); | ||
} | ||
|
||
public double calculateYield(int purchasingPrice) { | ||
return ((double) calculatePrizeMoneyTotal()) / purchasingPrice; | ||
} | ||
|
||
private long calculatePrizeMoneyTotal() { | ||
return statistics.keySet() | ||
.stream() | ||
.mapToLong(lottoRank -> lottoRank.getPrizeMoney() * statistics.get(lottoRank)) | ||
.sum(); | ||
} | ||
|
||
public Map<LottoRank, Long> getStatistics() { | ||
return Collections.unmodifiableMap(statistics); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package lotto.domain; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
public class LottoTicket { | ||
private static final int VALID_LOTTO_NUMBER_COUNTS = 6; | ||
private static final String INVALID_LOTTO_NUMBER_COUNTS = "로또 티켓은 중복되지 않은 6자리의 숫자로 구성되어야 합니다."; | ||
|
||
private final Set<LottoNumber> lottoNumbers; | ||
|
||
private LottoTicket(Set<LottoNumber> lottoNumbers) { | ||
validateNumberCounts(lottoNumbers.size()); | ||
this.lottoNumbers = lottoNumbers; | ||
} | ||
|
||
public static LottoTicket from(List<Integer> numbers) { | ||
validateNumberCounts(numbers.size()); | ||
Set<LottoNumber> lottoNumbers = numbers.stream() | ||
.map(LottoNumber::from) | ||
.collect(Collectors.toSet()); | ||
return new LottoTicket(lottoNumbers); | ||
} | ||
|
||
private static void validateNumberCounts(int numberCounts) { | ||
if (numberCounts != VALID_LOTTO_NUMBER_COUNTS) { | ||
throw new IllegalArgumentException(INVALID_LOTTO_NUMBER_COUNTS); | ||
} | ||
} | ||
|
||
public int checkMatchCounts(LottoTicket lottoTicket) { | ||
return (int) lottoNumbers.stream() | ||
.filter(lottoTicket::contains) | ||
.count(); | ||
} | ||
|
||
public boolean contains(LottoNumber lottoNumber) { | ||
return lottoNumbers.contains(lottoNumber); | ||
} | ||
|
||
public List<LottoNumber> getLottoNumbers() { | ||
return Collections.unmodifiableList(new ArrayList<>(lottoNumbers)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package lotto.domain; | ||
|
||
import java.util.Collections; | ||
import java.util.EnumMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
public class LottoTickets { | ||
private static final int LOTTO_TICKET_PRICE = 1000; | ||
private static final int ZERO = 0; | ||
private static final String NOT_ENOUGH_PURCHASING_MONEY = "금액이 부족하여 로또 티켓을 구매할 수 없습니다."; | ||
|
||
private final List<LottoTicket> lottoTickets; | ||
|
||
private LottoTickets(List<LottoTicket> lottoTickets) { | ||
this.lottoTickets = lottoTickets; | ||
} | ||
|
||
public static LottoTickets generateAutomatic(PurchasingPrice purchasingPrice, LottoNumberGenerator lottoNumberGenerator) { | ||
int purchasableTicketCounts = purchasingPrice.calculatePurchasableTicketCounts(LOTTO_TICKET_PRICE); | ||
validateTicketCounts(purchasableTicketCounts); | ||
List<LottoTicket> lottoTickets = Stream.generate(() -> LottoTicket.from(lottoNumberGenerator.generate())) | ||
.limit(purchasableTicketCounts) | ||
.collect(Collectors.toList()); | ||
return new LottoTickets(lottoTickets); | ||
} | ||
|
||
private static void validateTicketCounts(int purchasableTicketCounts) { | ||
if (purchasableTicketCounts == ZERO) { | ||
throw new IllegalArgumentException(NOT_ENOUGH_PURCHASING_MONEY); | ||
} | ||
} | ||
|
||
public LottoResult checkResult(WinningLottoTicket winningLottoTicket) { | ||
Map<LottoRank, Long> statistics = lottoTickets.stream() | ||
.map(winningLottoTicket::compareNumbers) | ||
.collect(Collectors.groupingBy(lottoRank -> lottoRank, () -> new EnumMap<>(LottoRank.class), | ||
Collectors.counting())); | ||
return new LottoResult(statistics); | ||
} | ||
|
||
public int getPurchasingPrice() { | ||
return lottoTickets.size() * LOTTO_TICKET_PRICE; | ||
} | ||
|
||
public int getTicketCounts() { | ||
return lottoTickets.size(); | ||
} | ||
|
||
public List<LottoTicket> getLottoTickets() { | ||
return Collections.unmodifiableList(lottoTickets); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package lotto.domain; | ||
|
||
public class PurchasingPrice { | ||
private static final int ZERO = 0; | ||
private static final String INVALID_PRICE = "로또 티켓 구매 금액은 양의 정수이어야 합니다."; | ||
|
||
private final int price; | ||
|
||
public PurchasingPrice(int price) { | ||
validatePrice(price); | ||
this.price = price; | ||
} | ||
|
||
private void validatePrice(int price) { | ||
if (price <= ZERO) { | ||
throw new IllegalArgumentException(INVALID_PRICE); | ||
} | ||
} | ||
|
||
public int calculatePurchasableTicketCounts(int ticketCost) { | ||
return price / ticketCost; | ||
} | ||
|
||
public int getPrice() { | ||
return price; | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
src/main/java/lotto/domain/RandomLottoNumberGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package lotto.domain; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
|
||
public class RandomLottoNumberGenerator implements LottoNumberGenerator { | ||
private static final List<Integer> NUMBERS = IntStream.rangeClosed(1, 45) | ||
.boxed() | ||
.collect(Collectors.toList()); | ||
private static final int FROM_INDEX = 0; | ||
private static final int TO_INDEX = 6; | ||
|
||
@Override | ||
public List<Integer> generate() { | ||
Collections.shuffle(NUMBERS); | ||
List<Integer> numbers = NUMBERS.subList(FROM_INDEX, TO_INDEX); | ||
return new ArrayList<>(numbers); | ||
} | ||
} |
Oops, something went wrong.