Skip to content
2 changes: 1 addition & 1 deletion src/main/java/io/suhan/lotto/Main.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.suhan.lotto;

import io.suhan.lotto.model.lotto.LottoController;
import io.suhan.lotto.controller.LottoController;

public class Main {
public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package io.suhan.lotto.model.lotto;
package io.suhan.lotto.controller;

import static io.suhan.lotto.model.executor.PurchaseExecutor.PRICE_PER_LOTTO;

import io.suhan.lotto.model.executor.DrawExecutor;
import io.suhan.lotto.model.executor.PurchaseExecutor;
import io.suhan.lotto.model.lotto.Lotto;
import io.suhan.lotto.model.lotto.LottoFactory;
import io.suhan.lotto.model.lotto.LottoNumber;
import io.suhan.lotto.model.lotto.LottoRegistry;
import io.suhan.lotto.model.lotto.LottoStatistics;
import io.suhan.lotto.view.InputView;
import io.suhan.lotto.view.OutputView;
import java.util.Set;
import java.util.stream.Collectors;

public class LottoController {
private final LottoRegistry registry;
Expand All @@ -16,7 +22,7 @@ public LottoController() {

public void run() {
try {
int balance = InputView.getBalance();
int balance = InputView.getValidBalance();

executePurchase(balance);
executeDraw(balance);
Expand All @@ -26,7 +32,13 @@ public void run() {
}

private void executePurchase(int balance) {
PurchaseExecutor purchaseExecutor = new PurchaseExecutor(registry, balance);
int manualCount = InputView.getValidManualCount();

if (PRICE_PER_LOTTO * manualCount > balance) {
throw new IllegalArgumentException("금액이 부족합니다.");
}

PurchaseExecutor purchaseExecutor = new PurchaseExecutor(registry, balance, manualCount);
purchaseExecutor.execute();

OutputView.printPurchaseResult(registry.getLottos());
Expand All @@ -35,7 +47,9 @@ private void executePurchase(int balance) {
private void executeDraw(int balance) {
Lotto winningLotto = createWinningLotto();

DrawExecutor drawExecutor = new DrawExecutor(registry, winningLotto);
LottoNumber bonusNumber = InputView.getValidBonusNumber();

DrawExecutor drawExecutor = new DrawExecutor(registry, winningLotto, bonusNumber);
drawExecutor.execute();

LottoStatistics statistics = new LottoStatistics(drawExecutor.getResults());
Expand All @@ -44,11 +58,8 @@ private void executeDraw(int balance) {
}

private Lotto createWinningLotto() {
Set<LottoNumber> wonNumbers = InputView.getWonNumbers()
.stream()
.map(LottoNumber::new)
.collect(Collectors.toSet());
Set<LottoNumber> wonNumbers = LottoFactory.toLottoNumbers(InputView.getValidWonNumbers());

return new Lotto(wonNumbers);
return Lotto.of(wonNumbers);
}
}
12 changes: 9 additions & 3 deletions src/main/java/io/suhan/lotto/model/DrawResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@

public class DrawResult {
private final int matchedCount;
private final Rank rank;

private DrawResult(int matchedCount) {
private DrawResult(int matchedCount, boolean bonusMatched) {
this.matchedCount = matchedCount;
this.rank = Rank.of(matchedCount, bonusMatched);
}

public static DrawResult of(int matchedCount) {
return new DrawResult(matchedCount);
public static DrawResult of(int matchedCount, boolean bonusMatched) {
return new DrawResult(matchedCount, bonusMatched);
}

public int getMatchedCount() {
return matchedCount;
}

public Rank getRank() {
return rank;
}
}
3 changes: 2 additions & 1 deletion src/main/java/io/suhan/lotto/model/NumberPool.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.suhan.lotto.model.lotto.Lotto;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class NumberPool {
Expand Down Expand Up @@ -39,6 +40,6 @@ private List<Integer> generateNumbersInRange(int from, int to) {
}

public List<Integer> getNumbers() {
return numbers;
return Collections.unmodifiableList(numbers);
}
}
48 changes: 48 additions & 0 deletions src/main/java/io/suhan/lotto/model/Rank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.suhan.lotto.model;

import java.util.Arrays;

public enum Rank {
FIRST(6, false, 2000000000, "6개 일치"),
SECOND(5, true, 30000000, "5개 일치, 보너스 볼 일치"),
THIRD(5, false, 1500000, "5개 일치"),
FOURTH(4, false, 50000, "4개 일치"),
FIFTH(3, false, 5000, "3개 일치"),
NONE(0, false, 0, ""); // fallback

private final int matchedCount;
private final boolean bonusRequired;
private final int prize;
private final String description;

Rank(int matchedCount, boolean bonusRequired, int prize, String description) {
this.matchedCount = matchedCount;
this.bonusRequired = bonusRequired;
this.prize = prize;
this.description = description;
}

public static Rank of(int matchedCount, boolean bonusMatched) {
return Arrays.stream(Rank.values())
.filter((rank) -> rank.getMatchedCount() == matchedCount)
.filter((rank) -> rank.isBonusRequired() == bonusMatched)
.findFirst()
.orElse(Rank.NONE);
}

public int getMatchedCount() {
return matchedCount;
}

public boolean isBonusRequired() {
return bonusRequired;
}

public int getPrize() {
return prize;
}

public String getDescription() {
return description;
}
}
19 changes: 17 additions & 2 deletions src/main/java/io/suhan/lotto/model/executor/DrawExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,26 @@
public class DrawExecutor implements Executor {
private final LottoRegistry registry;
private final Lotto winningLotto;
private final LottoNumber bonusNumber;
private final List<DrawResult> results;

public DrawExecutor(LottoRegistry registry, Lotto winningLotto) {
public DrawExecutor(LottoRegistry registry, Lotto winningLotto, LottoNumber bonusNumber) {
this.registry = registry;
this.winningLotto = winningLotto;
this.bonusNumber = bonusNumber;
this.results = new ArrayList<>();
}

@Override
public void execute() {
if (winningLotto.getNumbers().contains(bonusNumber)) {
throw new IllegalArgumentException("보너스 번호는 당첨 번호와 중복될 수 없습니다.");
}

for (Lotto lotto : registry.getLottos()) {
int matchedCount = calculateMatchedCount(lotto, winningLotto);
results.add(DrawResult.of(matchedCount));
boolean bonusMatched = isBonusMatched(matchedCount, lotto);
results.add(DrawResult.of(matchedCount, bonusMatched));
}
}

Expand All @@ -35,6 +42,14 @@ private int calculateMatchedCount(Lotto lotto, Lotto winningLotto) {
return numbers.size();
}

private boolean isBonusMatched(int matchedCount, Lotto lotto) {
if (matchedCount != Lotto.LOTTO_SIZE - 1) {
return false;
}

return lotto.getNumbers().contains(bonusNumber);
}

public List<DrawResult> getResults() {
return results;
}
Expand Down
32 changes: 27 additions & 5 deletions src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
package io.suhan.lotto.model.executor;

import io.suhan.lotto.model.lotto.Lotto;
import io.suhan.lotto.model.lotto.LottoFactory;
import io.suhan.lotto.model.lotto.LottoRegistry;
import io.suhan.lotto.model.lotto.LottoType;
import io.suhan.lotto.view.InputView;
import java.util.List;
import java.util.Set;

public class PurchaseExecutor implements Executor {
public static final int PRICE_PER_LOTTO = 1000;

private final LottoRegistry registry;
private final int balance;
private int balance;
private final int manualCount;

public PurchaseExecutor(LottoRegistry registry, int balance) {
public PurchaseExecutor(LottoRegistry registry, int balance, int manualCount) {
this.registry = registry;
this.balance = balance;
this.manualCount = manualCount;
}

@Override
public void execute() {
int count = getAvailableCount(balance);
if (manualCount > 0) {
purchaseManualNumbers();
}
Comment on lines +26 to +28

Choose a reason for hiding this comment

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

Controller에서 이미 로또 수 검증을 하고 있는데, 실행 단계에서도 같은 검증이 중복되고 있네요.
검증 로직은 하나만 두어도 괜찮을 거 같아요.

if (manualCount < 0) {
    throw new IllegalArgumentException("로또 수는 0 또는 양수만 입력할 수 있습니다.");
}

추가적으로 로또 수 검증 로직은 Controller에 두는 게 적절할 지, 아니면 Model에 두는 게 더 적절할지 한 번 고민해보시면 좋겠습니다. (정답은 없습니다 :))

Copy link
Author

Choose a reason for hiding this comment

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

현재 Controller에서는 manualCount가 음수인 경우를 1차적으로 필터링하고, 이후 PurchaseExecutor에서는 양수인 경우에만 InputView.getManualNumbers()를 호출하여 수동 번호를 입력받도록 하고 있습니다.

말씀하신대로 어느정도 중복되는 느낌이 들긴 하나, PurchaseExecutor에 있는 검증 로직(if (manualCount > 0))을 없앨 경우 InputView.getManualNumbers()가 무조건 1회는 실행되게 되어 0을 입력받게 되는 경우에도 수동으로 구매할 번호를 입력해 주세요. 메시지가 출력되는 문제가 있었습니다.

따라서 저런 방식으로 두번 검증하여 0인 경우에는 메시지 자체도 출력되지 않도록 구현하였는데, 혹시 더 좋은 방법이 있을까요?

Copy link

@c0mpuTurtle c0mpuTurtle Oct 13, 2025

Choose a reason for hiding this comment

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

"수동으로 구매할 번호를 입력해 주세요." 메시지가 출력되는 문제가 있었다는 사실은 몰랐네요.

다른 방법이라고 한다면 inputView에서 0을 바로 처리하는 방법도 있습니다.

ex) 0이 입력되면 바로 빈 배열을 return

하지만 이렇게 되면 코드에서 ‘빈 리스트를 반환하는 이유’가 명확히 드러나지 않고,
로직의 책임 분리 측면에서도 적합하지 않은 것 같습니다.

따라서, 저 역시 수한님께서 하신 방법을 사용할 거 같네요. 자세한 설명 감사드립니다 :)


int autoCount = getAvailableCount(balance);

for (int i = 0; i < autoCount; i++) {
registry.add(LottoFactory.createLotto(LottoType.AUTOMATIC));
}
}

private void purchaseManualNumbers() {
List<Set<Integer>> manualNumbersList = InputView.getValidManualNumbers(manualCount);

for (Set<Integer> numbers : manualNumbersList) {
Lotto lotto = Lotto.of(LottoType.MANUAL, LottoFactory.toLottoNumbers(numbers));

for (int i = 0; i < count; i++) {
registry.add(LottoFactory.createLotto());
registry.add(lotto);
balance -= PRICE_PER_LOTTO;
}
}

Expand Down
19 changes: 17 additions & 2 deletions src/main/java/io/suhan/lotto/model/lotto/Lotto.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.suhan.lotto.model.lotto;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
Expand All @@ -9,22 +10,36 @@ public class Lotto {
public static final int LOTTO_NUMBER_MIN = 1;
public static final int LOTTO_NUMBER_MAX = 45;

private final LottoType type;
private final Set<LottoNumber> numbers;

public Lotto(Set<LottoNumber> numbers) {
private Lotto(LottoType type, Set<LottoNumber> numbers) {
if (numbers.size() != LOTTO_SIZE) {
throw new IllegalArgumentException("로또 번호는 " + LOTTO_SIZE + "개여야 합니다.");
}

this.type = type;
this.numbers = new HashSet<>(numbers);
Copy link

@c0mpuTurtle c0mpuTurtle Oct 4, 2025

Choose a reason for hiding this comment

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

중복 제거를 위해 Set을 사용하신 거 맞죠? 😊 로또의 자료구조를 고민하셨다는 게 잘 느껴져서 좋게 봤습니다.

다만 Set의 대표적인 구현체인 HashSet, LinkedHashSet, TreeSet 중에서 HashSet을 선택하신 이유가 조금 궁금합니다.
예를 들어 로또 번호는 출력 시 정렬된 상태로 보여주기 때문에 TreeSet도 고려할 수 있을 것 같은데요.
혹시 HashSet을 선택하신 데 특별한 이유가 있으실까요?

Copy link
Author

Choose a reason for hiding this comment

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

네 맞습니다~! 로또 구조의 특성상 중복을 제거할 필요가 있다고 판단하여 Set을 통해 구현하였습니다.
여러 Set 구현체 중 HashSet을 선택한 이유는 다음과 같은데, 혹시 설명이 더 필요한 부분이 있다면 언제든지 말씀해주세요..!

구현체 중복 입력 순서 보장 정렬
HashSet X X X
LinkedHashSet X O X
TreeSet X X O
  1. 먼저 중복 조건의 경우 셋 다 허용하지 않기 때문에 모두 만족했습니다.
  2. 입력 순서 보장의 경우 굳이 보장할 필요가 없다고 생각하여 LinkedHashSet은 제외했습니다.
  3. 마지막으로 정렬의 경우 출력 예시를 보면 로또 번호들이 정렬되어 출력이 된 것을 확인할 수 있었습니다.
    따라서 초기에는 TreeSet을 적용하려고 했으나, TreeSet의 경우 Set의 원소 LottoNumber에 대해 Comparable<T>를 구현해야 하는 부담이 있었습니다.
    게다가 Set에 대한 연산을 수행하는 과정에서 원소들이 꼭 정렬되어있을 필요는 없다고 판단하여, 출력 시에만 정렬된 상태로 출력될 수 있도록 구현을 진행했습니다..!

}

public static Lotto of(Set<LottoNumber> numbers) {
return new Lotto(LottoType.AUTOMATIC, numbers);
}

public static Lotto of(LottoType type, Set<LottoNumber> numbers) {
return new Lotto(type, numbers);
}

@Override
public String toString() {
return numbers.stream().sorted(Comparator.comparingInt(LottoNumber::getValue)).toList().toString();
}

public LottoType getType() {
return type;
}

public Set<LottoNumber> getNumbers() {
return numbers;
return Collections.unmodifiableSet(numbers);
}
}
19 changes: 14 additions & 5 deletions src/main/java/io/suhan/lotto/model/lotto/LottoFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@
import io.suhan.lotto.model.NumberPool;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class LottoFactory {
public static Lotto createLotto() {
public static Lotto createLotto(LottoType type) {
NumberPool pool = NumberPool.of(Lotto.LOTTO_NUMBER_MIN, Lotto.LOTTO_NUMBER_MAX);
return createLotto(pool);
return createLotto(type, pool);
}

private static Lotto createLotto(NumberPool pool) {
public static Lotto createLotto(LottoType type, NumberPool pool) {
List<Integer> poolNumbers = new ArrayList<>(pool.getNumbers()); // copy
Collections.shuffle(poolNumbers);

Expand All @@ -23,6 +22,16 @@ private static Lotto createLotto(NumberPool pool) {
.map(LottoNumber::new)
.collect(Collectors.toSet());

return new Lotto(numbers);
return Lotto.of(type, numbers);
}

public static Set<LottoNumber> toLottoNumbers(Set<Integer> numbers) {
return numbers.stream()
.map(LottoNumber::new)
.collect(Collectors.toSet());
}

public static int getManualLottosCount(List<Lotto> lottos) {
return lottos.stream().filter((lotto) -> lotto.getType() == LottoType.MANUAL).toList().size();
}
}
3 changes: 2 additions & 1 deletion src/main/java/io/suhan/lotto/model/lotto/LottoRegistry.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.suhan.lotto.model.lotto;

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

public class LottoRegistry {
Expand All @@ -15,6 +16,6 @@ public void add(Lotto lotto) {
}

public List<Lotto> getLottos() {
return lottos;
return Collections.unmodifiableList(lottos);
}
}
Loading