Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
276d063
feat : 캐시이용을 원할하게 하기위해 LottoNumber 의 생성방식을 정적팩터리 메서드로 변경하고, LottoNumb…
Nov 30, 2025
f753801
doc : step4 과제 관련 기능목록 작성
Nov 30, 2025
77b6b2a
feat : 로또 수동 구매 갯수 지정 및 로또 수동발행 개발완료
Nov 30, 2025
b0f7b63
feat : 수동입력 추가한 로또 기능 개발
Nov 30, 2025
2b0a81d
feat :
Nov 30, 2025
77b813b
refactor : 정수대신 문자열 입력으로 변경하여 미입력 시 재 입력 로직 준비
Nov 30, 2025
a487493
refactor : 클래스명 수정
Nov 30, 2025
cc149cb
refactor
Nov 30, 2025
8c8a98f
fix : 수동입력 미반영 문제 해결
Nov 30, 2025
70ac1f6
feat : 잘못입력시 재 입력 유도하게 기능 추가
Nov 30, 2025
a4bb709
refactor : main 부분 메서드 정리
Nov 30, 2025
0419968
refactor : LottoNumber 와 LottoCache 를 통합하여 LottoNumber 에서 of로 새로운 객체를…
Dec 1, 2025
76893fe
fix : LottoNumber 생성자 접근을 차단하기 위해 record 에서 class 로 변경
Dec 1, 2025
85b53db
feat : 로또 생성방식을 쉽게 갈아끼우기 위한 인터페이스 & 구현(혼합 + 수동 + 자동)개발 완료
Dec 1, 2025
ca2a55d
fix : 잘못된 구현체 주입순서로 무조건 `LottoCombineGenerator` 가 주입되는 문제 해결
Dec 1, 2025
c351e9a
refactor : 복잡해진 인터페이스를 단순화하여서 변경
Dec 7, 2025
a9faec2
refactor : controller에서 interface 분리 원칙에 부합하지않는 조건문 분기로 구현체 정하는 방식 제거
Dec 7, 2025
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: 76 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,88 @@
`WinningLotto` : Lotto 에서 일치갯수와 보너스 맞는지 받아와 Rank 에 전달하여 당첨 결과를 WinningResult 에 전달
`Rank` : 규칙 정의 및 등수 판단

### 별도 메오
### 별도 메모
- `Rank` 와 `Lotto` 순환참조 없애기
- indent 가 2여서 그러는건데 이 경우에 스트림 쓰기 추천
- `bonus` 가 모든 번호 검증 -> 필요한거만
- 쓰지않는 패키지(ctl + opt + O)와, 포매팅 정리 (opt + cmd + L)


### 추가 개인 과제
- 난수생성에 대한 테스트 통제
- [x] 난수생성에 대한 테스트 통제 (보류)
- 현재 난수생성 방식은 static 메서드로 구현하여서 테스트 코드 내에서 Override가 불가능하다.

## 3차 피드백 후 리팩터링
- [x] : `LottoNumber`는 매번 인스턴스가 생성되기 때문에 인스턴스의 갯수가 너무 많아져 성능이 떨어질 수 있다. LottoNumber 인스턴스를 생성한 후 '재사용'할 수 있도록 구현한다. Map과 같은 곳에 인스턴스를 생성한 후 재사용하는 방법을 찾으면 됨
-> 방식 : 캐싱 구현
-> 방식 : 캐싱 구현

#### 객체 그래프
```text
Pay → LottoGame
LottoTickets → List<Lotto> → List<LottoNumber> ← LottoNumberCache
WinningLotto (Lotto + bonus LottoNumber)
Rank → WinningResult (Map<Rank, Integer>)
```

- 최종
- `Pay` : 입력받은 지불 금액에 대한 객체
- `LottoGame` : 로또 진행 흐름을 담당 (입력받은 금액을 갯수로 바꾼다.)
- `LottoTickets` : 입력받은 갯수에 따라 `Lotto` 객체 생성
- `Lotto` : 로또 역할, 입력받은 번호가 몇개 일치하는지 확인
- `LottoNumber` : 로또의 각 수에 대한 포장 객체
- `LottoNumberCache` : 로또 번호를 캐싱하여 로또번호 재생산 막는 역할
- `WinningLotto` : 당첨번호 관리 (Lotto + LottoNumber)
- `Rank` : 당첨 수와 금액의 정보를 가지고, 일치 갯수를 가지고 등수를 결정
- `WinningResult` : 당첨 당첨 통계 추리기



# 4단계 - 로또 (수동)
## 기본 안내
### 기능 요구사항
- 현재 로또 생성기는 자동 생성 기능만 제공한다. 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다.
- 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다.

## 핵심 요구 사항
- 구매 로또 시 수동으로 추첨하는 기능 추가
- 자동 생성 숫자, 수동 생성 번호 추가

## 기능목록
- [x] : 수동으로 구매 할 로또 수 입력
- [x] : 수동으로 구매하는 로또가 총 구매가능 수 보다 적은지
- [x] :로또 곧바로 자동 생성이 아닌 총 구매 가능 수 제시
- [x] : 넘었을 경우 다시 입력 유도
- [x] : 수동으로 구매할 로또 번호 입력
- [x] : 수동 로또 수만큼 반복입력한다
- [x] : 중복 입력, 1 ~ 45 범위 넘겨서 입력 시 다시 입력 유도
- 고민 : 입력 한번 할 때 마다 제시 or 모아서 넘기기
- [x] : 모아서 넘기기
- [x] : 로또 발행 결과 수동, 자동 갯수 카운트
- [x] : 수동부터 먼저 제시

## 클래스 다이어그램 설계
### 도메인 목록과 역할

#### 기존
- `Pay` : 입력받은 지불 금액에 대한 객체
- `LottoGame` : 로또 진행 흐름을 담당 (입력받은 금액을 갯수로 바꾼다.)
- `LottoTickets` : 입력받은 갯수에 따라 `Lotto` 객체 생성
- `Lotto` : 로또 역할, 입력받은 번호가 몇개 일치하는지 확인
- `LottoNumber` : 로또의 각 수에 대한 포장 객체
- `LottoNumberCache` : 로또 번호를 캐싱하여 로또번호 재생산 막는 역할
- `WinningLotto` : 당첨번호 관리 (Lotto + LottoNumber)
- `Rank` : 당첨 수와 금액의 정보를 가지고, 일치 갯수를 가지고 등수를 결정
- `WinningResult` : 당첨 당첨 통계 추리기

#### 추가
- `BuyLotto` : 로또 구매(자동 + 수동) 에 대한 관리
- `manualLotto` : 수동구매하는 로또와 그 갯수 관리

## 1차 피드백 후 리팩터링

- [ ] : 로또를 생성하는 부분 인터페이스로 분리해 보는 연습
- [x] : of 메서드를 활용하면 LottoNumber 객체가 재사용되는 것을 보장하지 못함
- `LottoNumber` 객체가 캐싱을 통해 재사용하는 것을 강제할 수 있도록 리팩터링
- `LottoNumber`와 `LottoNumberCache`를 통합
47 changes: 42 additions & 5 deletions src/main/java/lotto/controller/LottoApplication.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,59 @@
package lotto.controller;

import java.util.List;
import java.util.stream.IntStream;
import lotto.domain.business.LottoBuy;
import lotto.domain.business.LottoGame;
import lotto.domain.model.LottoGenerator;
import lotto.domain.model.WinningResult;
import lotto.domain.model.impl.LottoCombineGenerator;
import lotto.view.InputView;
import lotto.view.ResultView;

public class LottoApplication {

public static void main(String[] args) {
int pay = InputView.inputPurchaseAmount();
new LottoApplication().run();
}

private void run() {
while(true) {
try {
play();
return;
} catch(IllegalArgumentException e) {
ResultView.printError(e.getMessage());
}
}
}

private void play() {
String pay = InputView.inputPurchaseAmount();
String manualCount = InputView.inputManulNumber();

LottoGame lottoGame = new LottoGame(pay);
List<String> manualLottoNumbers = readManualLottos(manualCount);
LottoGenerator lottoGenerator = new LottoCombineGenerator(pay, manualLottoNumbers);
LottoBuy lottoBuy = lottoGenerator.generate();

ResultView.printAutoManualCount(lottoBuy.combineBuyCount());
LottoGame lottoGame = new LottoGame(pay, lottoBuy.combineLotto());
ResultView.printLottos(lottoGame.getLottos());

String winningNumbers = InputView.inputWinningNumbers();
int bonusNumbers = InputView.inputBonusNumbers();
WinningResult winningResult = lottoGame.calculateWinningResult(winningNumbers, bonusNumbers);
WinningResult winningResult = lottoGame.calculateWinningResult(InputView.inputWinningNumbers(), InputView.inputBonusNumbers());
String totalReturn = winningResult.calculateTotalReturn(pay);
ResultView.printResult(winningResult, totalReturn);
}

private List<String> readManualLottos(String count) {
if(count.isEmpty()) {
throw new IllegalArgumentException("수동 구매 수는 0 이상의 숫자로 입력해 주세요.");
}
int manualCount = Integer.parseInt(count);
if(manualCount > 0) {
InputView.inputManulMessage();
}
return IntStream.range(0, manualCount)
.mapToObj(i -> InputView.inputManulLotto())
.toList();
}
}
21 changes: 21 additions & 0 deletions src/main/java/lotto/domain/business/LottoBuy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package lotto.domain.business;

import java.util.stream.Stream;
import lotto.domain.model.Auto;
import lotto.domain.model.BuyCount;
import lotto.domain.model.LottoTickets;
import lotto.domain.model.Manual;

public record LottoBuy(BuyCount buyCount, Manual manual, Auto auto) {

public LottoTickets combineLotto() {
return new LottoTickets(Stream.concat(
this.manual.manualLottoList().stream(),
this.auto.autoLottoList().stream())
.toList());
}

public BuyCount combineBuyCount() {
return this.buyCount;
}
}
18 changes: 17 additions & 1 deletion src/main/java/lotto/domain/business/LottoGame.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,20 @@

public record LottoGame(Pay pay, LottoTickets lottoTickets) {

public LottoGame(String pay, LottoTickets lottoTickets) {
this(new Pay(pay), lottoTickets);
}

public LottoGame(int pay) {
this(new Pay(pay), generateLottos(new Pay(pay)));
this(new Pay(pay));
}

public LottoGame(int pay, LottoTickets lottoTickets) {
this(new Pay(pay), lottoTickets);
}

private LottoGame(Pay pay) {
this(pay, generateLottos(pay));
}

private static LottoTickets generateLottos(Pay pay) {
Expand All @@ -20,4 +32,8 @@ public List<Lotto> getLottos() {
public WinningResult calculateWinningResult(String winnerLottoNumber, int bonus) {
return this.lottoTickets.identifyWinners(new WinningLotto(bonus, winnerLottoNumber));
}

public WinningResult calculateWinningResult(String winnerLottoNumber, String bonus) {
return calculateWinningResult(winnerLottoNumber, Integer.parseInt(bonus));
}
}
19 changes: 19 additions & 0 deletions src/main/java/lotto/domain/model/Auto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package lotto.domain.model;

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

public record Auto(List<Lotto> autoLottoList) {

public Auto(int autoNumber) {
this(generateLottos(autoNumber));
}

private static List<Lotto> generateLottos(int num) {
return IntStream
.range(0, num)
.mapToObj(i -> new Lotto())
.toList();
}

}
14 changes: 14 additions & 0 deletions src/main/java/lotto/domain/model/BuyCount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package lotto.domain.model;

public record BuyCount(int total, int manual, int auto) {

public BuyCount {
validate(total, manual, auto);
}

void validate(int total, int manual, int auto) {
if(total != (manual + auto)) {
throw new IllegalArgumentException("지불금액 대비 구매수가 일치하지 않습니다.");
}
}
}
6 changes: 3 additions & 3 deletions src/main/java/lotto/domain/model/Lotto.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private static List<LottoNumber> getIntegers(int[] ints) {

private static List<LottoNumber> generateLottoNumberByInput(String lottoNumbers) {
return Arrays.stream(extractWinnerLottoNumber(lottoNumbers))
.map(LottoNumberCache::getLottoNumber)
.map(LottoNumber::of)
.toList();
}

Expand Down Expand Up @@ -77,13 +77,13 @@ private static List<Integer> generateNumberList() {

private static List<LottoNumber> convertToLottoNumbers(List<Integer> lottoNumbers) {
return lottoNumbers.stream()
.map(LottoNumberCache::getLottoNumber)
.map(LottoNumber::of)
.toList();
}

public List<Integer> numberValues() {
return this.numbers.stream()
.map(LottoNumber::value)
.map(LottoNumber::getValue)
.toList();
}

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/lotto/domain/model/LottoGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package lotto.domain.model;

import lotto.domain.business.LottoBuy;

public interface LottoGenerator {

LottoBuy generate();
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
LottoBuy generate();
LottoTickets generate();

LottoBuy를 생성해 반환하다보니 Auto인 경우에도 Manual, Manual인 경우에도 Auto 값을 가짐
각각의 Generator 구현체에서 바로 LottoTickets를 생성하도록 접근해 본다.
현재 구현과 어떻게 다른지 고민해 본다.

}
65 changes: 59 additions & 6 deletions src/main/java/lotto/domain/model/LottoNumber.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,71 @@
package lotto.domain.model;

public record LottoNumber(int value) {
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class LottoNumber {

private final int value;
private final static Map<Integer, LottoNumber> cache;
public static final int MIN_LOTTO_NUMBER = 1;
public static final int MAX_LOTTO_NUMBER = 45;

public LottoNumber(String value) {
static {
cache = getLottoNumberMap();
}

private static Map<Integer, LottoNumber> getLottoNumberMap() {
return IntStream.rangeClosed(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
.boxed()
.collect(
Collectors.toMap(
Function.identity(),
LottoNumber::new)
);
}

private LottoNumber(String value) {
this(Integer.parseInt(value));
}

public LottoNumber {
validate(value);
private LottoNumber(int value) {
this.value = value;
}

public static LottoNumber of(int value) {
LottoNumber cachedLottoNumber = cache.get(value);
validate(cachedLottoNumber);
return cachedLottoNumber;
}

private void validate(int value) {
if(value < 1 || value > 45) {
public static LottoNumber of(String value) {
return of(Integer.parseInt(value));
}

public int getValue() {
return value;
}

private static void validate(LottoNumber result) {
if(result == null) {
throw new IllegalArgumentException("로또 번호는 1~45사이 입력하시오");
}
}

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

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