Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
849e2fe
RaceLotto 클래스에 금액 입력→번호 생성→당첨번호→통계 출력까지 실행 흐름 추가
Sep 24, 2025
84ad2df
RaceLottoMethod에 로또 게임 핵심 로직(입력·생성·당첨·통계) 구현
Sep 24, 2025
cf566ce
InputView 클래스 구현 (구입 금액·당첨 번호 입력)
Sep 24, 2025
84fe5a6
OutputView 클래스 구현 (구입 금액·로또·통계 출력)
Sep 24, 2025
ee9214d
구입 금액을 포장하는 Price 값 객체 구현 (검증·로또 개수 계산)
Sep 24, 2025
b0320e9
LottoNumber를 원시값 포장 객체로 구현 (1~45 범위 검증 포함)
Sep 24, 2025
caad7b4
LottoNumbers를 일급 컬렉션으로 구현 (6개 검증·정렬 기능 포함)
Sep 24, 2025
3625fd2
여러 LottoNumbers를 보관·조회하는 LottoNumbersRepository 구현
Sep 24, 2025
3c52ba5
자동 로또 번호 생성 구현
Sep 24, 2025
5256990
Main 클래스 추가 – RaceLotto 실행 엔트리 포인트
Sep 24, 2025
6eaeb8f
test: Price 값 객체 생성·예외·로또 개수 계산 단위 테스트 추가
Sep 24, 2025
35d5b27
test: LottoNumbersRepository에 로또번호 추가 및 불변리스트 반환 단위 테스트 추가
Sep 24, 2025
1925a09
test: LottoNumbers 생성·예외·불변리스트·정렬 기능 단위 테스트 추가
Sep 24, 2025
59a8dbd
test: LottoNumber 유효성 및 예외 처리 단위 테스트 추가
Sep 24, 2025
3960e78
test: RandomLottoNumberGenerator가 항상 6개의 번호를 생성하는지 단위 테스트 추가
Sep 24, 2025
8f78037
README 추가
Sep 24, 2025
bf9eb51
Controller 클래스명 RaceLotto → AutoLotooController로 변경
Sep 27, 2025
8a1ed8f
검증 메서드명을 validate… 패턴으로 통일
Sep 28, 2025
ea203ae
로또 일치 결과 계산 메서드로 분리 및 이름 명확화
Sep 28, 2025
53e53c6
printLotteryStatistics가 matchCounts와 profitRate를 받아 통계와 수익률을 출력하도록 변경
Sep 28, 2025
c0d24e0
로또 일치 결과/수익률 계산 메서드 분리 (countMatchResults, calculateProfitRate)
Sep 28, 2025
2578a3e
AutoLottoController에 showLotteryStatistics 메서드 추가 및 통계 출력 로직 분리
Sep 28, 2025
3b73ece
Controller 보조 클래스 → 모델 비즈니스 로직 클래스(LottoBusiness)로 역할 변경 및 View 의존성 제거
Sep 29, 2025
8f4157d
AutoLottoController를 단계별 메서드(showPrice, showLottoNumbers, readLastLot…
Sep 29, 2025
fa8c3ae
변수명을 더 의미 있게 변경
Sep 29, 2025
dcc3c70
변수명을 더 의미 있게 변경
Sep 29, 2025
32ff258
Main에서 AutoLottoController 메서드들을 호출해 프로그램 실행하도록 구성
Sep 29, 2025
0293eee
로또 번호 생성 로직을 모델로 이동하고 전략 패턴 적용
Sep 30, 2025
5dcf37f
몇 개의 번호가 맞았는지를 표현하는 MatchResult enum 추가
Oct 1, 2025
a45f213
로또 통계 계산 로직을 int[]에서 EnumMap 기반으로 변경
Oct 1, 2025
d87205d
AutoLottoController에서 EnumMap 기반 통계 출력 적용
Oct 1, 2025
98ea697
도메인 객체들에 equals/hashCode 구현
Oct 1, 2025
036ccdb
테스트 용이성을 위해 LottoBusiness에 전략 패턴 적용
Oct 1, 2025
510b0c0
테스트 코드 추가
Oct 1, 2025
9f18556
feat: 로또 3단계(수동 구매) 및 4단계(보너스볼 당첨 결과) 구현
Oct 2, 2025
f74a8ac
클래스/메서드 네이밍 개선
Oct 2, 2025
fd6c52f
클래스 네이밍 개선
Oct 2, 2025
87965f2
test: 보너스볼 포함 케이스 테스트 추가
Oct 2, 2025
3c5d1a1
buyTickets 메서드 책임 분리 및 리팩터링
Oct 2, 2025
eb187e4
createInputLotto, countMatchResults 메서드 로직 분리
Oct 2, 2025
6d47438
README 수정
Oct 2, 2025
de36cc9
와일드카드 import 제거, InputView 패키지명 및 mergeRepositories 메서드명 변경
Oct 8, 2025
2393907
LottoNumbers getter의 중복 불변 처리 로직 제거
Oct 8, 2025
b32bfa9
matchLottoNumber의 if문 내부 로직을 containsNumber 메서드로 분리하여 depth 감소
Oct 8, 2025
28647f0
MatchResult enum의 불필요한 description 파라미터 제거
Oct 8, 2025
579c2f3
fromCount의 for문 로직을 메서드로 분리하여 depth 감소
Oct 8, 2025
450573a
FixedLottoNumberGenerator 예외 상황 테스트 추가
Oct 8, 2025
022567f
Fixture 적용으로 테스트 중복 제거 및 가독성 향상
Oct 8, 2025
fb04843
빈줄 출력을printBlankLine() 메서드 호출로 변경
Oct 11, 2025
e1680af
LottoNumbers → LottoTicket 이름 변경 및 equals/hashCode 제거
Oct 11, 2025
e4cddbd
지역 변수와 함수 인자명을 명확하게 변경
Oct 11, 2025
6a4da86
LottoNumber 스트림을 사용해 매칭 로직 단순화
Oct 11, 2025
58fe1f0
Stream을 활용해 MatchResult 조회 로직 단순화
Oct 11, 2025
7b87bf7
동일객체 테스트 제거
Oct 11, 2025
5cda9d9
PurchaseAmount를 도메인 모델로 이동
Oct 11, 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
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm'
}

group = 'cholog'
Expand All @@ -14,8 +15,12 @@ dependencies {
testImplementation platform('org.assertj:assertj-bom:3.25.1')
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation('org.assertj:assertj-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}

test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(17)
}
18 changes: 18 additions & 0 deletions src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 로또 미션

## 기능 요구사항

- 로또 당첨 번호를 받아 일치한 번호 수에 따라 당첨 결과를 보여준다.

## 프로그래밍 요구사항

- 모든 원시 값과 문자열을 포장한다.
- 일급 컬렉션을 쓴다.

## 추가 기능 요구사항

- 2등을 위한 보너스볼을 추첨한다.
- 당첨 통계에 2등을 추가한다.
- 2등 당첨 조건은 당첨 번호 5개 + 보너스 볼이다
- 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다.
- 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다.
30 changes: 30 additions & 0 deletions src/main/java/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import controller.LottoController;
import model.PurchaseAmount;
import model.LottoNumber;
import model.LottoTicket;
import model.LottoTicketBundle;

public class Main {
public static void main(String[] args) {
LottoController controller = new LottoController();

// 1. 구입 금액 입력
PurchaseAmount purchaseAmount = controller.askPurchaseAmount();

// 2. 수동 및 자동 로또 번호 생성
LottoTicketBundle allTickets = controller.buyTickets(purchaseAmount.calculateLottoCount());

// 3. 당첨 번호 입력
LottoTicket winningNumbers = controller.askWinningNumbers();

// 4. 보너스 볼 입력
LottoNumber bonusBall = controller.askBonusBall();

// 5. 통계 출력
controller.showStatistics(allTickets.readLottoNumbersRepository(),
winningNumbers.getNumbers(),
purchaseAmount.getValue(),
bonusBall
);
}
}
100 changes: 100 additions & 0 deletions src/main/java/controller/LottoController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package controller;

import view.InputView;
import view.OutputView;
import model.PurchaseAmount;
import model.LottoNumber;
import model.LottoTicket;
import model.LottoService;
import model.LottoTicketBundle;
import model.MatchResult;


import java.util.List;
import java.util.Map;

public class LottoController {
private final LottoService lottoService = new LottoService();
private final OutputView outView = new OutputView();
private final InputView inputView = new InputView();

private PurchaseAmount readPrice() {
outView.printInputPrice();
while (true) {
PurchaseAmount purchaseAmount = readPriceException();
if (purchaseAmount != null) return purchaseAmount;
}
}

private PurchaseAmount readPriceException() {
try {
return new PurchaseAmount(Integer.parseInt(inputView.inputPrice()));
} catch (NumberFormatException input) {
outView.printInvalidNumber();
} catch (IllegalArgumentException input) {
outView.printInvalidPrice();
}
return null;
}

public PurchaseAmount askPurchaseAmount() {
PurchaseAmount purchaseAmount = readPrice();
outView.printPriceValue(purchaseAmount.getValue());
outView.printBlankLine();
return purchaseAmount;
}

public LottoTicketBundle buyTickets(int ticketCount) {
int manualCount = requestManualTicketCount();
int autoCount = calculateAutoCount(ticketCount, manualCount);

LottoTicketBundle tickets = generateTickets(manualCount, autoCount);
displayTickets(manualCount, autoCount, tickets);

return tickets;
}

private int requestManualTicketCount() {
outView.printManualTicketCount();
return Integer.parseInt(inputView.inputManualCount());
}

private int calculateAutoCount(int totalTickets, int manualCount) {
return totalTickets - manualCount;
}

private LottoTicketBundle generateTickets(int manualCount, int autoCount) {
outView.printManualLottoNumbersPrompt();
List<String> manualInputs = inputView.inputManualLottos(manualCount);

LottoTicketBundle manualTickets = lottoService.createManualLottos(manualInputs);
LottoTicketBundle autoTickets = lottoService.createLottos(autoCount);

return lottoService.mergeAutoAndManualLottos(manualTickets, autoTickets);
}

private void displayTickets(int manualCount, int autoCount, LottoTicketBundle tickets) {
outView.printBuyCount(manualCount, autoCount);
outView.printLottos(tickets.readLottoNumbersRepository());
}

public LottoTicket askWinningNumbers() {
outView.printInputWinningNumbers();
LottoTicket winningLotto = lottoService.createInputLotto(inputView.inputWinningNumbers());
outView.printBlankLine();
return winningLotto;
}

public LottoNumber askBonusBall() {
outView.printBonusBall();
LottoNumber bonusNumber = new LottoNumber(Integer.parseInt(inputView.inputBonusBall()));
outView.printBlankLine();
return bonusNumber;
}

public void showStatistics(List<LottoTicket> allTickets, List<LottoNumber> winningNumbers, int purchaseAmount, LottoNumber bonusBall) {
Map<MatchResult, Integer> matchCounts = lottoService.countMatchResults(allTickets, winningNumbers, bonusBall);
String profitRate = lottoService.calculateProfitRate(matchCounts, purchaseAmount);
outView.printLotteryStatistics(matchCounts, profitRate);
}
}

Choose a reason for hiding this comment

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

전체적으로 Controller 클래스임에도 출력문이 직접적으로 작성되어 있는 것 같아요!
MVC 패턴을 적용시키신 김에 출력의 책임은 컨트롤러에서 제외하는 것이 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

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

저는 Controller가 출력까지 책임지는 것으로 이해하고 있었는데,
상희님 말씀대로라면 Controller가 그 책임을 갖지 않는 구조로 보입니다.
혹시 출력의 책임은 어느 계층에서 가져가는 게 더 적절할지 조언해주실 수 있을까요?

Copy link

@SANGHEEJEONG SANGHEEJEONG Oct 10, 2025

Choose a reason for hiding this comment

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

제가 말한 부분은 아래 코드인데요, 현재 print문은 모두 OutputView에서 작성해주신 것 같아요!
컨트롤러에서의 print문은 제거하고, OutputView가 출력의 책임을 모두 담당하게 하는 것이 어떨까요?

System.out.println();

Copy link
Author

Choose a reason for hiding this comment

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

view와 controller의 역할을 분리하려고 신경 썼는데, 말씀해주신 부분은 놓쳤네요!
System.out.println();을 printBlankLine() 메서드로 분리해서 OutputView에서 책임을 가지도록 수정했습니다.

21 changes: 21 additions & 0 deletions src/main/java/model/FixedLottoNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package model;

import java.util.List;

public class FixedLottoNumberGenerator implements LottoNumberGenerator {
private final List<LottoNumber> fixedNumbers;

public FixedLottoNumberGenerator(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
}
this.fixedNumbers = numbers.stream()
.map(LottoNumber::new)
.toList();
}

@Override
public LottoTicket generate() {
return new LottoTicket(fixedNumbers);
}
}
41 changes: 41 additions & 0 deletions src/main/java/model/LottoNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package model;

import java.util.Objects;

public class LottoNumber {
private final int number;

public LottoNumber(int number) {
validateRange(number);
this.number = number;
}

private void validateRange(int number) {
if (number < 1 || number > 45) {
throw new IllegalArgumentException("로또 번호는 1~45 사이여야 합니다");
}
}

public int getNumber() {
return number;
}

@Override
public String toString() {
return String.valueOf(number);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LottoNumber)) return false;
LottoNumber lottoNumber = (LottoNumber) o;
return number == lottoNumber.number;
}

@Override
public int hashCode() {
return Objects.hash(number);
}
}

5 changes: 5 additions & 0 deletions src/main/java/model/LottoNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package model;

public interface LottoNumberGenerator {
LottoTicket generate();
}
108 changes: 108 additions & 0 deletions src/main/java/model/LottoService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package model;


import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

public class LottoService {
private final LottoNumberGenerator generator;

public LottoService() {
this(new RandomLottoNumberGenerator());
}

public LottoService(LottoNumberGenerator generator) {
this.generator = generator;
}

public LottoTicket createOneLotto() {
return generator.generate();
}

public LottoTicketBundle createLottos(int count) {
LottoTicketBundle repository = new LottoTicketBundle();
for (int i = 0; i < count; i++) {
repository.addLottoNumbers(createOneLotto().sortNumbers());
}
return repository;
}

public LottoTicket createInputLotto(String inputNumber) {
List<LottoNumber> numbers = parseInputToNumbers(inputNumber);
return new LottoTicket(numbers);
}

private List<LottoNumber> parseInputToNumbers(String inputNumber) {
String[] rawNumbers = inputNumber.split(",");
List<LottoNumber> numbers = new ArrayList<>();
for (String raw : rawNumbers) {
numbers.add(new LottoNumber(Integer.parseInt(raw.trim())));
}
return numbers;
}

public LottoTicketBundle createManualLottos(List<String> inputs) {
LottoTicketBundle repository = new LottoTicketBundle();
for (String input : inputs) {
repository.addLottoNumbers(createInputLotto(input));
}
return repository;
}

public LottoTicketBundle mergeAutoAndManualLottos(LottoTicketBundle manual, LottoTicketBundle auto) {
LottoTicketBundle repository = new LottoTicketBundle();
for (LottoTicket lotto : manual.readLottoNumbersRepository()) {
repository.addLottoNumbers(lotto);
}

for (LottoTicket lotto : auto.readLottoNumbersRepository()) {
repository.addLottoNumbers(lotto);
}
return repository;
}

public Map<MatchResult, Integer> countMatchResults(List<LottoTicket> allLotteries,
List<LottoNumber> winningLotto,
LottoNumber bonusBall) {
Map<MatchResult, Integer> matchCounts = initializeMatchCounts();
for (LottoTicket lotto : allLotteries) {
int count = matchLottoNumber(lotto.getNumbers(), winningLotto);
boolean bonusMatch = matchBonus(lotto.getNumbers(), bonusBall, count);
MatchResult result = MatchResult.fromCount(count, bonusMatch);
matchCounts.put(result, matchCounts.get(result) + 1);
}
return matchCounts;
}

private Map<MatchResult, Integer> initializeMatchCounts() {
Map<MatchResult, Integer> counts = new EnumMap<>(MatchResult.class);
for (MatchResult result : MatchResult.values()) {
counts.put(result, 0);
}
return counts;
}

private int matchLottoNumber(List<LottoNumber> lotto, List<LottoNumber> winningLotto) {
return (int) lotto.stream()
.filter(winningLotto::contains)
.count();
}

private boolean matchBonus(List<LottoNumber> lotto, LottoNumber bonusBall, int count) {
if (count == 5) {
return lotto.contains(bonusBall);
}
return false;
}

public String calculateProfitRate(Map<MatchResult, Integer> matchCounts, int money) {
double profitRate = 0;
for (MatchResult result : matchCounts.keySet()) {
profitRate += matchCounts.get(result) * result.getReward();
}
profitRate = profitRate / (double) money;
return String.format("%.2f", profitRate);
}
}
29 changes: 29 additions & 0 deletions src/main/java/model/LottoTicket.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package model;

import java.util.*;

public class LottoTicket {
private final List<LottoNumber> numbers;

public LottoTicket(List<LottoNumber> numbers) {
validateSize(numbers);
this.numbers = List.copyOf(numbers);
}

private void validateSize(List<LottoNumber> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("로또 번호가 6개가 아닙니다");
}
}

public LottoTicket sortNumbers() {
List<LottoNumber> sortNumber = new ArrayList<>(numbers);
Collections.sort(sortNumber, Comparator.comparingInt(LottoNumber::getNumber));
return new LottoTicket(sortNumber);
}

public List<LottoNumber> getNumbers() {
return numbers;
}

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

public class LottoTicketBundle {
private final List<LottoTicket> repository = new ArrayList<>();

public void addLottoNumbers(LottoTicket lottoTicket) {
repository.add(lottoTicket);
}

public List<LottoTicket> readLottoNumbersRepository() {
return Collections.unmodifiableList(repository);
}

}
Loading