이 글은 프리코스 3주차 회고 글, 그리고 프리코스가 끝난 뒤 진행한 리팩토링 결과까지 포함하고 있다는 것을 미리 말씀드립니다!! 😀
리팩토링 후 달라진 점도 궁금하신 분들을 끝까지 다 봐주세요!~🧐🧐
3주 차 미션에 대한 글을 작성하기 이전에 먼저 2주 차 공통 피드백
내용들은 다음과 같습니다.
- README.md를 상세히 작성한다
- 기능 목록을 재검토한다
- 기능 목록을 업데이트한다
- 값을 하드 코딩하지 않는다
- 구현 순서도 코딩 컨벤션이다
- 변수 이름에 자료형은 사용하지 않는다
- 한 함수가 한 가지 기능만 담당하게 한다
- 함수가 한 가지 기능을 하는지 확인하는 기준을 세운다
- 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다
- 처음부터 큰 단위의 테스트를 만들지 않는다
2주 차 공통 피드백에 대해 자세히 보고 싶은 분은 2주 차 공통 피드백에서 볼 수 있습니다.
3주 차 미션을 구현하기 이전 2주 차 공통 피드백
내용들을 준수할 수 있도록 노력했습니다.
3주차 미션은 로또
를 구현하는 것이었습니다.
미션을 진행하는 방식은 다음과 같이 나와있었습니다.
🔍 진행 방식
- 미션은 기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항 세 가지로 구성되어 있다.
- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.
또한, 이전 미션과 다르게 이번 미션에선 제공된 Lotto
클래스를 활용해 구현하도록 되어있었습니다.
// 제공된 Lotto 클래스
public class Lotto {
private final List<Integer> numbers;
public Lotto(List<Integer> numbers) {
validate(numbers);
this.numbers = numbers;
}
private void validate(List<Integer> numbers) {
if (numbers.size != 6) {
throw new IllegalArgumentException();
}
}
// TODO: 추가 기능 구현
}
이전 주차와 마찬가지로, 요구 사항을 정확히 준수하기 위해 노력했습니다.
미션을 진행하기 전에 요구 사항을 꼼꼼히 분석하고 이를 토대로 구현할 기능 목록
을 작성하였습니다.
# 💸 미션 - 로또
## 📝 로또 게임 규칙
### 로또
- 로또 번호의 숫자 범위는 1~45까지이다.
- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
- 당첨은 1등부터 5등까지 있다.
- 당첨 기준
- 1등: 6개 번호 일치
- 2등: 5개 번호 일치 + 보너스 번호 일치
- 3등: 5개 번호 일치
- 4등: 4개 번호 일치
- 5등: 3개 번호 일치
- 당첨 순위별 금액
- 1등: 2,000,000,000원
- 2등: 30,000,000원
- 3등: 1,500,000원
- 4등: 50,000원
- 5등: 5,000원
### 입력
- 로또 구입 금액에 맞게 로또를 발행할 수 있다.
- 로또 1장의 가격은 1,000원이다.
- 당첨 번호를 쉼표(,)를 기준으로 입력받는다
- 보너스 번호를 입력받는다.
- 사용자가 잘못된 값을 입력하면 `IllegalArgumentException`을 발생시키고 다시 입력받는다.
### 출력
- 발행한 로또 수량 및 번호를 출력한다.
- 발행한 로또 번호는 오름차순으로 정렬하여 보여준다.
- 사용자의 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력한다.
- 수익률은 소수점 둘째 자리에서 반올림한다.
- 예외가 발생할 경우 `[ERROR]`로 시작하는 에러 메시지를 출력한다.
---
## 🕹구현할 기능 목록
### 로또 기능
- [x] 로또 번호는 1에서 45사이의 중복되지 않는 6개의 숫자를 가진다.
- [x] **[예외 처리]** 로또 번호의 개수가 6개가 아니면 예외가 발생한다.
- [x] **[예외 처리]** 로또 번호의 숫자들 중 1보다 작거나 45보다 큰 숫자가 있다면 예외가 발생한다.
- [x] **[예외 처리]** 6개의 숫자 중 중복되는 숫자가 있다면 예외가 발생한다.
### 로또 구입 기능
- [x] 로또 구입 금액에 해당하는 만큼 로또를 발행할 수 있다.
- [x] 로또 1장의 가격은 1,000원이다.
- [x] **[예외 처리]** 로또 구입 금액이 1,000원보다 작다면 예외가 발생한다.
- [x] **[예외 처리]** 로또 구입 금액이 1,000원으로 나누어 떨어지지 않으면 예외가 발생한다.
### 로또 생성 기능
- [x] 입력된 크기만큼 로또를 만들 수 있다.
### 보너스 번호 기능
- [x] 보너스 번호는 1부터 45사이의 숫자이다.
- [x] **[예외 처리]** 보너스 번호가 1보다 작거나 45보다 크다면 예외가 발생한다.
### 당첨 로또 기능
- [x] 당첨 로또는 중복되지 않는 6개의 숫자와 보너스 번호 1개를 가진다.
- [x] **[예외 처리]** 6개의 숫자 중 보너스 번호와 일치하는 숫자가 있으면 예외가 발생한다.
- [x] 1장의 로또와 당첨 번호를 비교하여 결과를 알 수 있다.
- [x] 1등: 6개 번호 일치
- [x] 2등: 5개 번호 일치 + 보너스 번호 일치
- [x] 3등: 5개 번호 일치
- [x] 4등: 4개 번호 일치
- [x] 5등: 3개 번호 일치
### 당첨 상금 기능
- [x] 당첨 순위별 상금이 얼마인지 알 수 있다.
- [x] 1등: 2,000,000,000원
- [x] 2등: 30,000,000원
- [x] 3등: 1,500,000원
- [x] 4등: 50,000원
- [x] 5등: 5,000원
### 당첨 통계 기능
- [x] 사용자가 구매한 총 로또와 당첨 번호를 비교하여 전체 당첨 내역을 알 수 있다.
- [x] 사용자가 구매한 로또 번호의 결과를 통해 수익률을 계산할 수 있다.
### 변환 기능
- [x] 문자열에서 숫자 리스트로 변환할 수 있다.
- [x] 쉼표(,)를 기준으로 숫자를 분리한다.
### 입력 기능
- [x] **[공통 예외 처리]** 입력이 공백이면 예외가 발생한다.
- [x] 로또 구입 금액을 입력 받는다.
- [x] **[예외 처리]** 로또 구입 금액에 대한 입력이 숫자가 아니면 예외가 발생한다.
- [x] 당첨 번호를 입력 받는다.
- [x] **[예외 처리]** 당첨 번호에 대한 입력 형식이 올바르지 않으면 예외가 발생한다.
- [x] 보너스 번호를 입력 받는다.
- [x] **[예외 처리]** 보너스 번호에 대한 입력이 숫자가 아니면 예외가 발생한다.
- [x] 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, 그 부분부터 입력을 다시 받는다.
### 출력 기능
- [x] 발행한 로또 수량 및 번호를 출력한다.
- [x] 로또 번호는 오름차순으로 정렬하여 출력한다.
- [x] 당첨 내역을 출력한다.
- [x] 로또 결과에 대한 총 수익률을 출력한다.
- [x] 수익률은 소수점 둘째 자리에서 반올림하여 출력한다.
- [x] 예외 상황 시 에러 문구를 출력한다.
- [x] 에러 문구는 "[ERROR]"로 시작해야 한다.
---
## 🚨 과제 제출 전 체크 리스트
- [x] 요구 사항에 명시된 출력값 형식을 지켰는지 확인
- [x] 예외 발생시 `[ERROR]`로 시작하는지 확인
- [x] 수익률은 소수점 둘째 자리에서 반올림하는지 확인
- [x] 로또 번호가 오름차순으로 정렬하여 출력하는지 확인
- [x] 모든 테스트가 성공하는지 확인
- [x] `./gradlew clean test`가 정상 통과하는지 확인
- [x] `ApplicationTest`가 정상 통과하는지 확인
- [x] 도메인 로직에 단위 테스트를 구현했는지 확인
- [x] 자바 17버전으로 정상 작동되는지 확인
- [x] 프로그램 실행의 시작점이 Application의 main()인지 확인
- [x] indent depth가 3이 넘지 않는지 확인
- [x] 3항 연산자를 쓰지 않았는지 확인
- [x] 함수의 길이가 15라인을 넘어가지 않도록 확인
- [x] else 예약어를 쓰지 않았는지 확인
- [x] switch/case를 쓰지 않았는지 확인
- [x] Java Enum을 적용했는지 확인
- [x] `Randoms` 및 `Console` API를 사용했는지 확인
- [x] Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms` 사용했는지 확인
- [x] 입력은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 사용했는지 확인
- [x] 로또 클래스 확인
- [x] 로또 클래스의 numbers의 접근 제어자가 private인지 확인
- [x] 로또 클래스의 인스턴스 변수를 추가했는지 확인(추가하는 것을 허용하지 않음)
이건 제가 3주 차 미션을 진행하기 이전 로또 게임 규칙
과 구현할 기능 목록
을 작성한 내용들입니다.
이전 주차와 달라진 것은, 이전 주차에는 기능 목록에서 클래스명까지 생각하고 작성하였습니다.
하지만 2주 차 공통 피드백
에 아래와 같이 글이 작성되어있었습니다.
기능 목록을 클래스 설계와 구현, 함수(메서드) 설계와 구현과 같이 너무 상세하게 작성하지 않는다. 클래스 이름, 함수(메서드) 시그니처와 반환값은 언제든지 변경될 수 있기 때문이다...
마치 저의 기능 목록을 보고 공통 피드백을 작성하셨나 느꼈을만큼, 이전까지 기능 목록을 작성하는 방법이 잘못되었다는 것을 알게 되었습니다.
그래서 변경 가능한 클래스명이나 메서드명 등을 최대한 작성하지 않을 수 있도록 신경쓰며 작성해보았습니다.
보너스 번호
에 대한 값을 검증하기 위해 원시값을 포장하여 미션을 진행하였습니다.
이번 미션에서 보너스 번호
의 숫자 범위는 1~45
까지였습니다.
이러한 보너스 번호
에 대한 숫자 범위를 쉽게 검증하기 위해 BonusNumber
라는 객체를 구현하여 이 안에 검증 관련 코드를 위치시켰습니다.
public class BonusNumber {
private static final String OUT_OF_RANGE_NUMBER_EXCEPTION_FORMAT = "보너스 번호는 %d부터 %d 사이의 숫자여야 합니다.";
private static final int MINIMUM_LOTTO_NUMBER = 1;
private static final int MAXIMUM_LOTTO_NUMBER = 45;
private final int bonusNumber;
public BonusNumber(int bonusNumber) {
validateBonusNumber(bonusNumber);
this.bonusNumber = bonusNumber;
}
private void validateBonusNumber(int bonusNumber) {
if (isOutOfRange(bonusNumber)) {
throw new IllegalArgumentException(
String.format(OUT_OF_RANGE_NUMBER_EXCEPTION_FORMAT, MINIMUM_LOTTO_NUMBER, MAXIMUM_LOTTO_NUMBER));
}
}
private boolean isOutOfRange(int bonusNumber) {
return bonusNumber < MINIMUM_LOTTO_NUMBER || bonusNumber > MAXIMUM_LOTTO_NUMBER;
}
}
물론,
기본 로또 번호
또한 숫자 범위는1~45
까지였지만 위에서 말씀드린대로 주어진Lotto
클래스를 이용하여 구현해야되었기 때문에 로또 번호에 대한 원시값 포장은 따로 진행하지 않고, 로또 번호는 단순히List<Integer>
타입을 이용하여 구현하였습니다.
이처럼 우아한테크코스 프리코스를 진행하며 느꼈던 것 중 하나는, 주어진 요구 사항을 읽다 보면 이 부분은 원시값을 포장하여 미션을 진행하면 좋겠다라는 생각
을 자연스레 느낄 수 있었습니다!! 👍
이번 미션에서 예외적인 부분
을 계속해서 생각해보고, 작성해보았습니다.
이를 통해, 추려낼 수 있었던 검증 부분들은 다음과 같습니다.
다음과 같이 검증 처리를 핵심
도메인 부분
과입력 부분
으로 나눈 이유는, 제 개인적인 생각으로 검증 처리는view 영역
과domain 영역
모든 부분에서 하는 것이 옳다고 느껴졌기 때문입니다.view 영역
에서는 입력 값이 올바른지 판단하기 위해 단순한 검증만 진행하고,domain 영역
에서 핵심적인 검증 기능을 수행할 수 있도록 구현하였습니다.
이번 미션에서 추가된 요구 사항 중 Java Enum을 적용한다.
라는 요구 사항이 있었습니다.
Enum 객체를 활용하기 위해 로또 등수
에 대한 값을 Enum을 활용하여 구현해볼 수 있었습니다.
public enum LottoRanking {
FIRST(6, false, 2000000000),
SECOND(5, true, 30000000),
THIRD(5, false, 1500000),
FOURTH(4, false, 50000),
FIFTH(3, false, 5000),
NOTHING(0, false, 0);
private static final int VALUE_TO_DETERMINE_SECOND_OR_THIRD = 5;
private final int numberOfMatches;
private final boolean hasBonusNumber;
private final int prizeMoney;
LottoRanking(int numberOfMatches, boolean hasBonusNumber, int prizeMoney) {
this.numberOfMatches = numberOfMatches;
this.hasBonusNumber = hasBonusNumber;
this.prizeMoney = prizeMoney;
}
public static LottoRanking of(int numberOfMatches, boolean hasBonusNumber) {
if (numberOfMatches != VALUE_TO_DETERMINE_SECOND_OR_THIRD) {
return findRanking(numberOfMatches, false);
}
return findRanking(numberOfMatches, hasBonusNumber);
}
private static LottoRanking findRanking(int numberOfMatches, boolean hasBonusNumber) {
return Arrays.stream(values())
.filter(lottoRanking -> lottoRanking.isMatch(numberOfMatches, hasBonusNumber))
.findFirst()
.orElse(NOTHING);
}
private boolean isMatch(int numberOfMatches, boolean hasBonusNumber) {
return this.numberOfMatches == numberOfMatches && this.hasBonusNumber == hasBonusNumber;
}
}
다음과 같이 로또와 일치하는 숫자 개수와 보너스 번호 일치 여부를 이용하여 로또 등수를 매길 수 있도록 구현하였습니다.
여기서 주의한 점은, 보너스 번호 일치 여부는 2등과 3등일 경우만 이를 구분하기 위해 필요하고, 다른 경우는 보너스 번호 일치 여부는 중요하지 않습니다.
이를 위해, 다음과 같이 구현해볼 수 있었습니다.
public static LottoRanking of(int numberOfMatches, boolean hasBonusNumber) {
if (numberOfMatches != VALUE_TO_DETERMINE_SECOND_OR_THIRD) {
return findRanking(numberOfMatches, false);
}
return findRanking(numberOfMatches, hasBonusNumber);
}
로또와 일치하는 숫자 개수가 5가 아닐 경우는 findRanking() 메서드에 false 값을 전달하고, 맞을 경우는 전달된 hasBonusNumber 값을 그대로 사용하여 로또 등수를 구하도록 구현하였습니다.
2주 차 공통 피드백에서 다음과 같은 말이 명시되어 있었다.
문자열, 숫자 등의 값을 하드 코딩하지 마라. 상수(static final)를 만들고 이름을 부여해 이 변수의 역할이 무엇인지 의도를 드러내라...
이를 통해 이전까지는 예외 메시지 등을 상수로 처리하지 않았더라면, 이번 미션에서는 모든 예외 메시지까지 상수로 처리할 수 있도록 구현하였습니다.
// 로또 클래스 예외 메시지 예시
private static final String INVALID_NUMBERS_SIZE_EXCEPTION_FORMAT = "로또는 총 %d개의 번호로 이루어져야 합니다.";
private static final String OUT_OF_RANGE_NUMBER_EXCEPTION_FORMAT = "로또 번호는 %d부터 %d 사이의 숫자여야 합니다.";
private static final String DUPLICATED_NUMBER_EXCEPTION_MESSAGE = "로또 번호들 중 중복된 숫자가 존재합니다.";
모든 도메인 로직에 대한 테스트 코드를 작성하기 위해 노력했습니다.
이전 미션에서 테스트 코드를 통해 오류를 쉽게 찾을 수 있었다는 장점을 느꼈다면, 이번 미션에서는 리팩토링 과정에서 여러 구현 방법을 적용하면서, 놓친 부분들을 테스트 코드의 오류를 통해 쉽게 감지할 수 있다는 장점을 느낄 수 있었습니다.
벌써 절반 이상을 진행하며 벌써 아쉽기도 하며, 한편으로 목표를 가지고 성장할 수 있는 기회를 준 우테코에게 감사의 말을 전하고 싶다!! 😀
저의 자세한 코드가 궁금하신 분은 아래 링크에서 확인할 수 있습니다.
https://github.com/jcoding-play/java-lotto-6/tree/gyungchan
이 부분은 프리코스가 끝나고 난 뒤 리팩토링을 진행하고 이전과 크게 달라진 점을 알려드리기 위해 작성해보았습니다!!
이번 미션에서는 제공된 Lotto 클래스
를 활용하도록 명시되어 있었기 때문에, 로또 번호
에 대한 원시값 포장을 진행하지 못하였었다.
하지만 지금은 혼자 리팩토링을 진행하는 것이기에 로또 번호에 대한 값도 LottoNumber
라는 객체를 이용하여 원시값을 포장하도록 구현하였다.
public class Lotto {
// 바뀐 부분
private final List<LottoNumber> numbers;
public Lotto(List<Integer> numbers) {
validate(numbers);
this.numbers = mapLottoNumber(numbers);
}
private List<LottoNumber> mapLottoNumber(List<Integer> numbers) {
return numbers.stream()
.map(LottoNumber::new)
.toList();
}
...
}
public class LottoNumber {
private static final int MINIMUM_LOTTO_NUMBER = 1;
private static final int MAXIMUM_LOTTO_NUMBER = 45;
private final int lottoNumber;
public LottoNumber(int lottoNumber) {
validateLottoNumber(lottoNumber);
this.lottoNumber = lottoNumber;
}
private void validateLottoNumber(int lottoNumber) {
if (lottoNumber < MINIMUM_LOTTO_NUMBER || lottoNumber > MAXIMUM_LOTTO_NUMBER) {
throw new IllegalArgumentException(
String.format("로또 번호는 %d부터 %d 사이의 숫자여야 합니다.", MINIMUM_LOTTO_NUMBER, MAXIMUM_LOTTO_NUMBER));
}
}
}
또한 로또 구입 금액에 대한 값
도 원시값을 포장하여 구현할 수 있도록 리팩토링하였다.
이전에는 로또 구입 금액에 대한 값을 검증하기 위해 LottoStore
클래스에 buyLotto 메서드 내부에서 검증을 진행하였다.
// 리팩토링 전 LottoStore 클래스
public class LottoStore {
private static final String LESS_THAN_MINIMUM_PRICE_EXCEPTION_FORMAT = "로또 구입 금액은 최소 %d원 이상이어야 합니다.";
private static final String INVALID_PURCHASE_AMOUNT_EXCEPTION_FORMAT = "로또 구입 금액은 %d원 단위어야 합니다.";
private static final int VALID_REMAINING_AMOUNT = 0;
private final LottoGenerator lottoGenerator;
public LottoStore(LottoGenerator lottoGenerator) {
this.lottoGenerator = lottoGenerator;
}
public List<Lotto> buyLotto(int purchaseAmount) {
validatePurchaseAmount(purchaseAmount);
return lottoGenerator.createLottos(purchaseAmount / LOTTO_PRICE);
}
private void validatePurchaseAmount(int purchaseAmount) {
if (purchaseAmount < LOTTO_PRICE) {
throw new IllegalArgumentException(
String.format(LESS_THAN_MINIMUM_PRICE_EXCEPTION_FORMAT, LOTTO_PRICE));
}
if (isInvalidPurchaseAmount(purchaseAmount)) {
throw new IllegalArgumentException(
String.format(INVALID_PURCHASE_AMOUNT_EXCEPTION_FORMAT, LOTTO_PRICE));
}
}
private boolean isInvalidPurchaseAmount(int purchaseAmount) {
return purchaseAmount % LOTTO_PRICE != VALID_REMAINING_AMOUNT;
}
}
하지만 이를 다음과 같이 PurchaseAmount
라는 객체를 이용하여 구입 금액에 대한 검증 기능을 위치시키고, LottoStore 클래스 내부에서는 이와 관련된 기능만을 수행하도록 리팩토링하였다.
public class PurchaseAmount {
private final int purchaseAmount;
public PurchaseAmount(int purchaseAmount) {
validatePurchaseAmount(purchaseAmount);
this.purchaseAmount = purchaseAmount;
}
private void validatePurchaseAmount(int purchaseAmount) {
if (purchaseAmount < Constants.LOTTO_PRICE) {
throw new IllegalArgumentException(
String.format("구입 금액은 최소 %,d원 이상이어야 합니다.", Constants.LOTTO_PRICE));
}
if (isInvalidPurchaseAmount(purchaseAmount)) {
throw new IllegalArgumentException(
String.format("구입 금액은 %,d원 단위어야 합니다.", Constants.LOTTO_PRICE));
}
}
}
// 리팩토링 후 LottoStore 클래스
public class LottoStore {
public Lottos buyLotto(PurchaseAmount purchaseAmount) {
int result = purchaseAmount.divideByLottoPrice();
return generateLottos(result);
}
private Lottos generateLottos(int size) {
return Stream.generate(this::generateLotto)
.limit(size)
.collect(Collectors.collectingAndThen(Collectors.toList(), Lottos::new));
}
private Lotto generateLotto() {
return new Lotto(pickUniqueNumbers());
}
private List<Integer> pickUniqueNumbers() {
return Randoms.pickUniqueNumbersInRange(MINIMUM_LOTTO_NUMBER, MAXIMUM_LOTTO_NUMBER, LOTTO_NUMBERS_SIZE);
}
}
이전에 로또 등수를 구하기 위해 if 문을 이용하여 구할 수 있었다.
이를 깔끔하게 변경하기 위한 방법을 찾아보다가 BiPredicate라는 함수형 인터페이스를 적용하게 되었습니다.
@FunctionalInterface
public interface BiPredicate<T, U> {
boolean test(T t, U u);
}
이는 2개의 인자를 통해 참, 거짓을 반환해주는 함수형 인터페이스입니다.
BiPredicate을 적용함으로 if문을 이용하여 등수를 구했던 이전과 달리 간단히 로또 등수를 구할 수 있었습니다.
// 변경 전 LottoRanking
public enum LottoRanking {
FIRST(6, false, 2000000000),
SECOND(5, true, 30000000),
THIRD(5, false, 1500000),
FOURTH(4, false, 50000),
FIFTH(3, false, 5000),
NOTHING(0, false, 0);
private static final int VALUE_TO_DETERMINE_SECOND_OR_THIRD = 5;
private final int numberOfMatches;
private final boolean hasBonusNumber;
private final int prizeMoney;
LottoRanking(int numberOfMatches, boolean hasBonusNumber, int prizeMoney) {
this.numberOfMatches = numberOfMatches;
this.hasBonusNumber = hasBonusNumber;
this.prizeMoney = prizeMoney;
}
public static LottoRanking of(int numberOfMatches, boolean hasBonusNumber) {
if (numberOfMatches != VALUE_TO_DETERMINE_SECOND_OR_THIRD) {
return findRanking(numberOfMatches, false);
}
return findRanking(numberOfMatches, hasBonusNumber);
}
private static LottoRanking findRanking(int numberOfMatches, boolean hasBonusNumber) {
return Arrays.stream(values())
.filter(lottoRanking -> lottoRanking.isMatch(numberOfMatches, hasBonusNumber))
.findFirst()
.orElse(NOTHING);
}
private boolean isMatch(int numberOfMatches, boolean hasBonusNumber) {
return this.numberOfMatches == numberOfMatches && this.hasBonusNumber == hasBonusNumber;
}
}
// 변경 후 LottoRanking
public enum LottoRanking {
FIRST((numberOfMatches, hasBonusNumber) -> numberOfMatches == 6, 2_000_000_000),
SECOND((numberOfMatches, hasBonusNumber) -> numberOfMatches == 5 && hasBonusNumber, 30_000_000),
THIRD((numberOfMatches, hasBonusNumber) -> numberOfMatches == 5, 1_500_000),
FOURTH((numberOfMatches, hasBonusNumber) -> numberOfMatches == 4, 50_000),
FIFTH((numberOfMatches, hasBonusNumber) -> numberOfMatches == 3, 5_000),
NOTHING((numberOfMatches, hasBonusNumber) -> numberOfMatches == 0, 0);
private final BiPredicate<Integer, Boolean> predicate;
private final int prizeMoney;
LottoRanking(BiPredicate<Integer, Boolean> predicate, int prizeMoney) {
this.predicate = predicate;
this.prizeMoney = prizeMoney;
}
public static LottoRanking of(int numberOfMatches, boolean hasBonusNumber) {
return Arrays.stream(values())
.filter(lottoRanking -> lottoRanking.isMatch(numberOfMatches, hasBonusNumber))
.findFirst()
.orElse(NOTHING);
}
private boolean isMatch(int numberOfMatches, boolean hasBonusNumber) {
return predicate.test(numberOfMatches, hasBonusNumber);
}
}