Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 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
2f0f94c
refactor : 로또 생성기에서 곧바로 LottoBuy 가 아닌 LottoTickets 으로 리턴하게 변경
Dec 9, 2025
676d135
refactor : LottoManulGenerator 에 불필요한 pay 필드 제거
Dec 9, 2025
7a06675
docs : 개발 과정 메모
Dec 9, 2025
e6f4e03
refactor : 불필요한 `Manual`, 'Auto' 객체 제거하여 곧바로 생성가능하게 변경
Dec 12, 2025
870094c
refactor : LottoCombineGenerator 의 generateTickets 메서드에서 단순 조립에서 comp…
Dec 12, 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
112 changes: 108 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,120 @@
`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] : 로또를 생성하는 부분 인터페이스로 분리해 보는 연습
- [x] : of 메서드를 활용하면 LottoNumber 객체가 재사용되는 것을 보장하지 못함
- `LottoNumber` 객체가 캐싱을 통해 재사용하는 것을 강제할 수 있도록 리팩터링
- `LottoNumber`와 `LottoNumberCache`를 통합

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

- [x] : 오히려 복잡해진 인터페이스 분리를 단순화하여서 구현
- 이유
-
1. 이전에는 인터페이스 구현체들은 필드가 모두 같아야한다는 것으로 잘못된 이해를 한점

- 그 이유는 인터페이스 이용을 스프링 bean 생성 때만 주로 해봤기 때문
-
2. 필드가 같아야한다는 잘못된 이해로 메서드 인자는 모두 같게, 그 내부 로직을 달리해서 구현한 점
- 이러한 이유 때문에 오히려 인터페이스가 더 복잡해짐
- [x] : Controller 에서 분기 처리에 따라 인터페이스 구현체 갈아끼우는 것 제거하기
- 조건문으로 같은 일(모드, 도메인)의 규칙 갈아끼우기에 불과함
- 이렇게 인터페이스 분리하면 장점
- 클라이언트 코드가 당장 필요한 구현체로 쉽게 갈아끼울수 있게 변경

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

- [x] : LottoGenerator 가 LottoBuy 가 아닌 LottoTickets 을 생성하게 하기
- [x] : LottoGenerator 구현체에서 바로 LottoTickets를 생성하도록 접근하기
- [x] : LottoManulGenerator 에서 불필요한 pay 제거
- 자동, 수동, 혼합 간에 필드는 비슷하게 pay, pay + lotto, lotto 각각 상황에 따라 인자가 달라짐
- 즉 충분히 필드가 다를 수있다.

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

- [x] : 이미 `LottoAutoGenerator`, `LottoManualGenerator` 각각의 구현체가 있는데 굳이 수동 자동관련 `Auto`, `Manual` 객체가 필요한지 고민할 것
- 고민 : 기존에는 Auto, Manual 이 필요할 수있었다. 그러나 인터페이스 구현체가 그 역할을 대체한다
- [ ] : composite 패턴을 이용해 part 인 구현체 `LottoAutoGenerator`, `LottoManualGenerator` 를 합친 composite 인 `LottoCombineGenerator` 를 이용한다
- 그러면 필드도 두개 구현체를 합친 인터페이스의 List로 한다 그리고 기존 생성도 두개를 각각 넣어주는 방식으로 구현한다
- 즉 여러 LottoGenerator 를 묶어서 두개를 하나처럼 쓸 수 있게 됨
45 changes: 40 additions & 5 deletions src/main/java/lotto/controller/LottoApplication.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,57 @@
package lotto.controller;

import java.util.List;
import java.util.stream.IntStream;
import lotto.domain.business.LottoGame;
import lotto.domain.model.LottoGenertor.LottoCombineGenerator;
import lotto.domain.model.LottoGenertor.LottoGenerator;
import lotto.domain.model.WinningResult;
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);

ResultView.printAutoManualCount(lottoGenerator.getBuyCount());
LottoGame lottoGame = new LottoGame(pay, lottoGenerator.generateTickets());
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();
}
}
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));
}
}
27 changes: 27 additions & 0 deletions src/main/java/lotto/domain/model/BuyCount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package lotto.domain.model;

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

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

private void validate(int total, int manual, int auto) {
if(total != (manual + auto)) {
throw new IllegalArgumentException("지불금액 대비 구매수가 일치하지 않습니다.");
}
}

public BuyCount add(BuyCount other) {
return new BuyCount(
this.total + other.total,
this.manual + other.manual,
this.auto + other.auto
);
}

public static BuyCount empty() {
return new BuyCount(0, 0, 0);
}

}
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package lotto.domain.model.LottoGenertor;

import java.util.List;
import java.util.stream.IntStream;
import lotto.domain.model.BuyCount;
import lotto.domain.model.Lotto;
import lotto.domain.model.LottoTickets;
import lotto.domain.model.Pay;

public class LottoAutoGenerator implements LottoGenerator {

int pay;

public LottoAutoGenerator(String pay) {
this(Integer.parseInt(pay));
}

public LottoAutoGenerator(int pay) {
this.pay = pay;
}

@Override
public LottoTickets generateTickets() {
return new LottoTickets(generateLottos(getTotalNumber(pay)).stream().toList());
}

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

@Override
public BuyCount getBuyCount() {
return new BuyCount(getTotalNumber(pay), 0, getTotalNumber(pay));
}

private int getTotalNumber(int pay) {
return new Pay(pay).convertToBuyCount();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package lotto.domain.model.LottoGenertor;

import java.util.List;
import java.util.stream.Stream;
import lotto.domain.model.BuyCount;
import lotto.domain.model.LottoTickets;
import lotto.domain.model.Pay;

public class LottoCombineGenerator implements LottoGenerator {

private final List<LottoGenerator> generators;

public LottoCombineGenerator(int pay, String manualLottoNumbers) {
this(pay, List.of(manualLottoNumbers));
}

public LottoCombineGenerator(String pay, List<String> manualLottoNumbers) {
this(Integer.parseInt(pay), manualLottoNumbers);
}

public LottoCombineGenerator(int pay, List<String> manualLottoNumbers) {
this(toLottosGenerators(pay, manualLottoNumbers));
}

public LottoCombineGenerator(List<LottoGenerator> generators) {
this.generators = generators;
}

@Override
public LottoTickets generateTickets() {
return new LottoTickets(generators.
stream()
.map(LottoGenerator::generateTickets)
.flatMap(t -> t.tickets().stream())
.toList());
}

private static List<LottoGenerator> toLottosGenerators(int pay, List<String> manualLottoNumbers) {
int remainPay = new Pay(pay).calculateRemainingPayment(manualLottoNumbers.size());
return Stream.of(
new LottoManulGenerator(manualLottoNumbers),
new LottoAutoGenerator(remainPay))
.toList();
}

@Override
public BuyCount getBuyCount() {
return generators.stream()
.map(LottoGenerator::getBuyCount)
.reduce(BuyCount.empty(), BuyCount::add);
}
}
11 changes: 11 additions & 0 deletions src/main/java/lotto/domain/model/LottoGenertor/LottoGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package lotto.domain.model.LottoGenertor;

import lotto.domain.model.BuyCount;
import lotto.domain.model.LottoTickets;

public interface LottoGenerator {

LottoTickets generateTickets();

BuyCount getBuyCount();
}
Loading