로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.
로또 자판기
구매 가능한 로또 수량 출력
구매한 로또 번호를 저장한다.
구매한 로또 번호 출력
당첨 내역 계산
당첨 내역 출력
수익률 출력
실행 결과 예시
구입금액을 입력해 주세요.
8000
8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[1, 3, 5, 14, 22, 45]
당첨 번호를 입력해 주세요.
1,2,3,4,5,6
보너스 번호를 입력해 주세요.
7
당첨 통계
---
3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다.
우선 위 처럼 출력이안된다. Input을 받기전에 출력하는 메시지들을 실행 결과 예시를 안보고 입출력 요구 사항만을 보고 구현했는데, 입출력 요구 사항에 따라 입력관련 메시지를 출력하라는 부분이 없어서 따로 구현을 안했었다.... 요구사항 문서를 자세하게 보지 않아서 일어난 실수이다.
로또 당첨 등수를 2등인지, 3등인지 구분하는 코드이다.
LottoRanking class
public static LottoRanking findByLottoRanking(int matchCount, Boolean hasBonus) {
if (hasBonus) {
return SECOND;
}
return Arrays.stream(LottoRanking.values())
.filter(ranking -> ranking.isWin(matchCount))
.findAny()
.orElse(EMPTY);
}
PrizeChecker enum class
private boolean checkBonus(int countMatch, List<Integer> lotto, int bonusNumber) {
return (countMatch == 5 && hasBonus(lotto, bonusNumber));
}
private boolean hasBonus(List<Integer> lotto, int bonusNumber) {
return lotto.stream().anyMatch(number -> number.equals(bonusNumber));
}
PrizeChecker 클래스가 2등인지 판별해서 LottoRanking을 생성한다.
나는 PrizeChecker라는 클래스가 2등인지 3등인지 판별하는게 더 자연스럽다고 생각해서 LottoRanking을 생성하는 findByLottoRanking 메서드에 로또 번호가 보너스번호를 가지고 5개 맞는다면 SECOND를 리턴하라고 했는데, 변수명이 hasBonus라 리뷰하는 사람도 그렇고 나도 다시 코드를 읽었을때, LottoRanking 클래스만 보고 코드에 오류가 있는줄 알았다.
hasBonus는 보너스 번호를 가지고 있다는 것만 표시하고 로또 번호가 5개 맞다는걸 알 수 없는 변수명인 것 같아서 "isSecond"로 매개변수 명을 고치는게 좋을 것 같다.
PrizeChecker class
public PrizeChecker(LottoCompany lottoCompany, List<Lotto> lottoBundle) {
List<Integer> winningNumber = lottoCompany.getWinningNumber();
int bonusNumber = lottoCompany.getBonusNumber();
initPrizeResult();
for (Lotto lotto : lottoBundle) {
List<Integer> lottoNumber = lotto.getNumbers();
int countMatch = countingMatch(winningNumber, lottoNumber);
boolean isBonus = checkBonus(countMatch, lottoNumber, bonusNumber);
LottoRanking lottoRanking = LottoRanking.findByLottoRanking(countMatch, isBonus);
this.prizeResult.put(lottoRanking, this.prizeResult.getOrDefault(lottoRanking, 0) + 1);
}
this.prizeMoney = sumPrize();
}
Lotto 클래스를 객체 스럽게 사용하지 않고, getter를 통해 로또의 값을 받아서 PrizeChecker에서 검사하고 있다.
public class Lotto {
private final List<Integer> numbers;
public boolean contains(int number) {
// 숫자가 포함되어 있는지 확인한다.
...
}
public int matchCount(Lotto other) {
// 당첨 번호와 몇 개가 일치하는지 확인한다.
...
}
}
위 코드 처럼 Lotto getter가 아닌 메시지를 보내야한다.
출처 - getter를 사용하는 대신 객체에 메시지를 보내자
가장 어려웠던 부분은 else를 쓰지 않는 것이였다.
else를 없애는 가장 간단한 방법은 elarly return 방법이다 if문을 여러 개 사용하고
public String getProductName(String productCode) {
if ("1".equals(productCode)) {
return "할부";
}
if ("2".equals(productCode)) {
return "리스";
}
if ("3".equals(productCode)) {
return "담보";
}
if ("4".equals(productCode)){
return "신용";
}
throw new IllegalArgumentException("존재하지 않는 상품입니다.");
}
if - return 기반으로 코드를 작성하면, 코드를 읽을 때 의식의 흐름이 메소드의 동작을 위한 '참' 조건을 기반으로 일어난다. 'A인 경우'를 기반으로 생각하는 것이 'A가 아닌경우' 를 기반으로 생각하는 것 보다는 훨씬 명확하고 쉽다.
참고 - [객체지향 생활체조 원칙] 규칙 2. else 예약어를 쓰지 않는다
elarly return을 통해 조건 분기점이 많아진다는 것은, 클래스의 구조를 변경해야 함을 의미할 확률이 높다. 위 로직을 enum을 활용하면 상품정보의 변경이 일어나더라도, enum 내부에서 모두 처리할 수 있는 유연한 코드가 된다.
public enum LoanProduct {
INSTALLMENT("1", "할부"),
LEASE("2", "리스"),
COLLATERAL("3", "담보"),
CREDIT("4", "신용")
;
private String productCode;
private String productName;
LoanProduct(String productCode, String productName) {
this.productCode = productCode;
this.productName = productName;
}
public static String productName(String productCode) {
return Arrays.stream(values())
.filter(bankingProduct -> bankingProduct.productCode.equals(productCode))
.findFirst()
.orElseThrow( () -> new IllegalArgumentException("상품이 존재하지 않습니다."))
.name();
}
}
public String getProductName(String productCode) {
return LoanProduct.productName(productCode);
}
enum이라는 걸 배운적이 있지만, C에서 사용 했을때 상수처럼 사용하는 정도로 사용했기 때문에 java enum을 적용하는 것도 어려웠다.
Java Enum 활용기