Repository: next-step/java-lotto
Pull request #4105: Step2
Author: login: bo-10000
Created: 2025-03-31T10:31:46Z | Merged: 2025-04-03T16:36:29Z



Diff:
diff --git a/src/main/java/Application.java b/src/main/java/Application.java
new file mode 100644
index 00000000000..1f2fe1baf18
--- /dev/null
+++ b/src/main/java/Application.java
@@ -0,0 +1,8 @@
+import controller.LottoController;
+
+public class Application {
+    public static void main(String[] args) {
+        LottoController lottoController = new LottoController();
+        lottoController.run();
+    }
+}
diff --git a/src/main/java/Calculator.java b/src/main/java/Calculator.java
deleted file mode 100644
index fb245c51852..00000000000
--- a/src/main/java/Calculator.java
+++ /dev/null
@@ -1,37 +0,0 @@
-public class Calculator {
-    private static void validate_input(String expression) {
-        if (expression == null || expression.isBlank()) {
-            throw new IllegalArgumentException("입력 값이 null이거나 빈 공백 문자입니다.");
-        }
-    }
-
-    private static int updateResult(int result, Operand operand, int number) {
-        if (operand == null) {
-            return number;
-        } 
-        else {
-            return operand.operate(result, number);
-        }
-    }
-
-    public static int run(String expression) {
-        validate_input(expression);
-
-        int result = 0;
-        Operand operand = null;
-
-        String[] tokens = expression.split(" ");
-        for (int i = 0; i < tokens.length; i++) {
-            String t = tokens[i];
-            if (i % 2 == 0) {
-                int number = Integer.parseInt(t);
-                result = updateResult(result, operand, number);
-            }
-            else {
-                operand = new Operand(t);
-            }
-        }
-
-        return result;
-    }
-}
diff --git a/src/main/java/Operand.java b/src/main/java/Operand.java
deleted file mode 100644
index 6f826053f2b..00000000000
--- a/src/main/java/Operand.java
+++ /dev/null
@@ -1,42 +0,0 @@
-class Operand {
-    private final String VALUE;
-
-    Operand(String value) {
-        this.VALUE = value;
-    }
-
-    private int sum(int num1, int num2) {
-        return num1 + num2;
-    }
-
-    private int subtract(int num1, int num2) {
-        return num1 - num2;
-    }
-
-    private int multiply(int num1, int num2) {
-        return num1 * num2;
-    }
-
-    private int divide(int num1, int num2) {
-        return num1 / num2;
-    }
-
-    int operate(int num1, int num2) {
-        if (VALUE.equals("+")) {
-            return sum(num1, num2);
-        } 
-        else if (VALUE.equals("-")) {
-            return subtract(num1, num2);
-        } 
-        else if (VALUE.equals("*")) {
-            return multiply(num1, num2);
-        } 
-        else if (VALUE.equals("/")) {
-            return divide(num1, num2);
-        } 
-        else {
-            throw new IllegalArgumentException("지원하지 않는 연산자입니다.");
-        }
-    }
-
-}
diff --git a/src/main/java/constant/LottoConstants.java b/src/main/java/constant/LottoConstants.java
new file mode 100644
index 00000000000..98e2ade58a5
--- /dev/null
+++ b/src/main/java/constant/LottoConstants.java
@@ -0,0 +1,15 @@
+package constant;
+
+import java.util.Map;
+
+public class LottoConstants {
+    public static final int LOTTO_NUMBER_COUNT = 6;
+    public static final int MIN_LOTTO_NUMBER = 1;
+    public static final int MAX_LOTTO_NUMBER = 45;
+    public static final Map<Integer, Integer> MATCH_PRICES = Map.of(
+            3, 5_000,
+            4, 50_000,
+            5, 1_500_000,
+            6, 2_000_000_000
+    );
+}
diff --git a/src/main/java/controller/LottoController.java b/src/main/java/controller/LottoController.java
new file mode 100644
index 00000000000..a109ba741c4
--- /dev/null
+++ b/src/main/java/controller/LottoController.java
@@ -0,0 +1,41 @@
+package controller;
+
+import domain.model.MatchResult;
+import view.InputView;
+import view.ResultView;
+import domain.model.LottoNumbers;
+import domain.model.LottoWallet;
+import domain.engine.LottoMachine;
+import domain.generator.RandomLottoNumberGenerator;
+
+
+public class LottoController {
+    private final LottoMachine lottoMachine = new LottoMachine(new RandomLottoNumberGenerator());
+
+    public void run() {
+        int purchaseAmount = InputView.getPurchaseAmount();
+        LottoWallet lottoWallet = purchase(purchaseAmount);
+
+        MatchResult result = draw(lottoWallet);
+
+        calculateProfit(purchaseAmount, result);
+    }
+
+    private LottoWallet purchase(int purchaseAmount) {
+        LottoWallet lottoWallet = this.lottoMachine.buyLottos(purchaseAmount);
+        ResultView.printPurchasedLottos(lottoWallet);
+        return lottoWallet;
+    }
+
+    private MatchResult draw(LottoWallet lottoWallet) {
+        LottoNumbers winNumbers = new LottoNumbers(InputView.getWinNumbers());
+        MatchResult result = lottoWallet.countMatches(winNumbers);
+        ResultView.printMatchResult(result);
+        return result;
+    }
+
+    private void calculateProfit(int purchaseAmount, MatchResult result) {
+        double profit = result.calculateProfit(purchaseAmount);
+        ResultView.printProfit(profit);
+    }
+}
diff --git a/src/main/java/domain/engine/LottoMachine.java b/src/main/java/domain/engine/LottoMachine.java
new file mode 100644
index 00000000000..983b75b9a25
--- /dev/null
+++ b/src/main/java/domain/engine/LottoMachine.java
@@ -0,0 +1,25 @@
+package domain.engine;
+
+import domain.generator.LottoNumberGenerator;
+import domain.model.LottoNumbers;
+import domain.model.LottoWallet;
+
+
+public class LottoMachine {
+    private static final int LOTTO_PRICE = 1000;
+
+    private final LottoNumberGenerator lottoNumberGenerator;
+
+    public LottoMachine(LottoNumberGenerator lottoNumberGenerator) {
+        this.lottoNumberGenerator = lottoNumberGenerator;
+    }
+
+    public LottoWallet buyLottos(int purchaseAmount) {
+        int numLottos = purchaseAmount / LOTTO_PRICE;
+        LottoWallet lottoWallet = new LottoWallet();
+        for (int i = 0; i < numLottos; i++) {
+            lottoWallet.addLotto(new LottoNumbers(this.lottoNumberGenerator.generate()));
+        }
+        return lottoWallet;
+    }
+}
diff --git a/src/main/java/domain/generator/LottoNumberGenerator.java b/src/main/java/domain/generator/LottoNumberGenerator.java
new file mode 100644
index 00000000000..5c79bae2fb4
--- /dev/null
+++ b/src/main/java/domain/generator/LottoNumberGenerator.java
@@ -0,0 +1,7 @@
+package domain.generator;
+
+import java.util.List;
+
+public interface LottoNumberGenerator {
+    List<Integer> generate();
+}
diff --git a/src/main/java/domain/generator/RandomLottoNumberGenerator.java b/src/main/java/domain/generator/RandomLottoNumberGenerator.java
new file mode 100644
index 00000000000..2d6410416d6
--- /dev/null
+++ b/src/main/java/domain/generator/RandomLottoNumberGenerator.java
@@ -0,0 +1,23 @@
+package domain.generator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class RandomLottoNumberGenerator implements LottoNumberGenerator {
+    private static final List<Integer> CACHED_NUMBERS = new ArrayList<>();
+    static {
+        for (int i = MIN_LOTTO_NUMBER; i <= MAX_LOTTO_NUMBER; i++) {
+            CACHED_NUMBERS.add(i);
+        }
+    }
+
+    @Override
+    public List<Integer> generate()  {
+        List<Integer> allNumbers = new ArrayList<>(CACHED_NUMBERS);
+        Collections.shuffle(allNumbers);
+        List<Integer> lottoNumbers = allNumbers.subList(0, LOTTO_NUMBER_COUNT);
+        Collections.sort(lottoNumbers);
+        return new ArrayList<>(lottoNumbers);
+    }
+}
diff --git a/src/main/java/domain/model/LottoNumbers.java b/src/main/java/domain/model/LottoNumbers.java
new file mode 100644
index 00000000000..5e582219119
--- /dev/null
+++ b/src/main/java/domain/model/LottoNumbers.java
@@ -0,0 +1,50 @@
+package domain.model;
+
+import java.util.List;
+
+import static constant.LottoConstants.*;
+
+public class LottoNumbers {
+    private final List<Integer> numbers;
+
+    public LottoNumbers(List<Integer> numbers) {
+        this.numbers = numbers;
+        validate();
+    }
+
+    public List<Integer> getNumbers() {
+        return this.numbers;
+    }
+
+    int countMatch(LottoNumbers winNumbers) {
+        return (int) winNumbers.getNumbers().stream()
+                .filter(this.numbers::contains)
+                .count();
+    }
+
+    private void validate() {
+        validateSize();
+        validateRange();
+        validateNoDuplicates();
+    }
+
+    private void validateSize() {
+        if (numbers.size() != LOTTO_NUMBER_COUNT) {
+            throw new IllegalArgumentException("Lotto numbers must contain exactly 6 numbers.");
+        }
+    }
+
+    private void validateRange() {
+        boolean hasInvalid = numbers.stream()
+                .anyMatch(n -> n < MIN_LOTTO_NUMBER || n > MAX_LOTTO_NUMBER);
+        if (hasInvalid) {
+            throw new IllegalArgumentException("Lotto numbers must be between 1 and 45.");
+        }
+    }
+
+    private void validateNoDuplicates() {
+        if (numbers.stream().distinct().count() != numbers.size()) {
+            throw new IllegalArgumentException("Lotto numbers must not contain duplicates.");
+        }
+    }
+}
diff --git a/src/main/java/domain/model/LottoWallet.java b/src/main/java/domain/model/LottoWallet.java
new file mode 100644
index 00000000000..2920b1bdf0e
--- /dev/null
+++ b/src/main/java/domain/model/LottoWallet.java
@@ -0,0 +1,29 @@
+package domain.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class LottoWallet {
+    private final List<LottoNumbers> lottos = new ArrayList<>();
+
+    public void addLotto(LottoNumbers lotto) {
+        this.lottos.add(lotto);
+    }
+
+    public MatchResult countMatches(LottoNumbers winNumbers) {
+        MatchResult matchResult = new MatchResult();
+        for (LottoNumbers lotto : lottos) {
+            matchResult.addMatchCount(lotto.countMatch(winNumbers));
+        }
+        return matchResult;
+    }
+
+    public int getCount() {
+        return this.lottos.size();
+    }
+
+    public List<LottoNumbers> getLottos() {
+        return this.lottos;
+    }
+}
diff --git a/src/main/java/domain/model/MatchResult.java b/src/main/java/domain/model/MatchResult.java
new file mode 100644
index 00000000000..e4cf2dbcc4c
--- /dev/null
+++ b/src/main/java/domain/model/MatchResult.java
@@ -0,0 +1,33 @@
+package domain.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static constant.LottoConstants.MATCH_PRICES;
+
+public class MatchResult {
+    private Map<Integer, Integer> resultMap = new HashMap<>();
+
+    public double calculateProfit(int purchaseAmount) {
+        return (double) getPrize() / purchaseAmount * 100;
+    }
+
+    public int getCount(int matchCount) {
+        return this.resultMap.getOrDefault(matchCount, 0);
+    }
+
+    void addMatchCount(int matchCount) {
+        if (MATCH_PRICES.containsKey(matchCount)) {
+            this.resultMap.put(matchCount, this.resultMap.getOrDefault(matchCount, 0) + 1);
+        }
+    }
+
+    private int getPrize() {
+        int totalPrize = 0;
+        for (int matchCount : this.resultMap.keySet()) {
+            int count = this.resultMap.get(matchCount);
+            totalPrize += MATCH_PRICES.get(matchCount) * count;
+        }
+        return totalPrize;
+    }
+}
diff --git a/src/main/java/utils/Calculator.java b/src/main/java/utils/Calculator.java
new file mode 100644
index 00000000000..89c9d8b9869
--- /dev/null
+++ b/src/main/java/utils/Calculator.java
@@ -0,0 +1,26 @@
+package utils;
+
+public class Calculator {
+    private static void validateInput(String expression) {
+        if (expression == null || expression.isBlank()) {
+            throw new IllegalArgumentException("입력 값이 null이거나 빈 공백 문자입니다.");
+        }
+    }
+
+    public static int run(String expression) {
+        validateInput(expression);
+
+        String[] tokens = expression.split(" ");
+
+        int result = Integer.parseInt(tokens[0]);
+        int i = 1;
+        while (i < tokens.length - 1) {
+            Operator operator = Operator.fromSymbol(tokens[i]);
+            int operand = Integer.parseInt(tokens[i+1]);
+            result = operator.calculate(result, operand);
+            i += 2;
+        }
+
+        return result;
+    }
+}
diff --git a/src/main/java/utils/Operator.java b/src/main/java/utils/Operator.java
new file mode 100644
index 00000000000..3387ce38bcb
--- /dev/null
+++ b/src/main/java/utils/Operator.java
@@ -0,0 +1,46 @@
+package utils;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+enum Operator {
+    ADD("+", (num1, num2) -> num1 + num2),
+    SUBTRACT("-", (num1, num2) -> num1 - num2),
+    MULTIPLY("*", (num1, num2) -> num1 * num2),
+    DIVIDE("/", (num1, num2) -> num1 / num2);
+
+    private static final Map<String, Operator> SYMBOL_MAP;
+
+    static {
+        SYMBOL_MAP = Arrays.stream(Operator.values()).collect(
+            Collectors.toMap(Operator::getSymbol, Function.identity())
+        );
+    }
+
+    private final String symbol;
+    private final BiFunction<Integer, Integer, Integer> calFunction;
+
+    Operator(String symbol, BiFunction<Integer, Integer, Integer> calFunction) {
+        this.symbol = symbol;
+        this.calFunction = calFunction;
+    }
+
+    static Operator fromSymbol(String symbol) {
+        Operator operator = SYMBOL_MAP.get(symbol);
+        if (operator == null) {
+            throw new IllegalArgumentException("지원하지 않는 연산자입니다.");
+        }
+        return operator;
+    }
+
+    String getSymbol() {
+        return this.symbol;
+    }
+
+    int calculate(Integer num1, Integer num2) {
+        return this.calFunction.apply(num1, num2);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java
new file mode 100644
index 00000000000..8fca31695ba
--- /dev/null
+++ b/src/main/java/view/InputView.java
@@ -0,0 +1,36 @@
+package view;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+import java.util.stream.Collectors;
+
+public class InputView {
+    private static final Scanner scanner = new Scanner(System.in);
+
+    public static int getPurchaseAmount() {
+        return getIntInput("구입금액을 입력해 주세요.");
+    }
+
+    public static List<Integer> getWinNumbers() {
+        String stringInput = getStringInput("지난 주 당첨 번호를 입력해 주세요.");
+        return parseIntFromString(stringInput);
+    }
+
+    private static List<Integer> parseIntFromString(String input) {
+        return Arrays.stream(input.split(","))
+                .map(String::trim)
+                .map(Integer::parseInt)
+                .collect(Collectors.toList());
+    }
+
+    private static int getIntInput(String message) {
+        String stringInput = getStringInput(message);
+        return Integer.parseInt(stringInput);
+    }
+
+    private static String getStringInput(String message) {
+        System.out.print(message);
+        return scanner.nextLine();
+    }
+}
diff --git a/src/main/java/view/ResultView.java b/src/main/java/view/ResultView.java
new file mode 100644
index 00000000000..2279447d47a
--- /dev/null
+++ b/src/main/java/view/ResultView.java
@@ -0,0 +1,49 @@
+package view;
+
+import domain.model.LottoNumbers;
+import domain.model.LottoWallet;
+import domain.model.MatchResult;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static constant.LottoConstants.MATCH_PRICES;
+
+public class ResultView {
+    public static void printPurchasedLottos(LottoWallet lottoWallet) {
+        int count = lottoWallet.getCount();
+        printMessage(count + "개를 구매했습니다.");
+        for (LottoNumbers lotto : lottoWallet.getLottos()) {
+            printLottoNumbers(lotto.getNumbers());
+        }
+    }
+
+    public static void printMatchResult(MatchResult matchResults) {
+        printMessage("당첨 통계");
+        printMessage("---------");
+        for (int matchCount : MATCH_PRICES.keySet()) {
+            int price = MATCH_PRICES.get(matchCount);
+            int count = matchResults.getCount(matchCount);
+            printMessage(matchCount + "개 일치 (" + price + "원)- " + count + "개");
+        }
+    }
+
+    public static void printProfit(double profit) {
+        printMessage("총 수익률은 " + String.format("%.2f", profit) + "입니다.");
+    }
+
+    public static void printMessage(String message) {
+        System.out.println(message);
+    }
+
+    private static void printLottoNumbers(List<Integer> lottoNumbers) {
+        String formattedNumbers = arrayToString(lottoNumbers);
+        printMessage(formattedNumbers);
+    }
+
+    private static String arrayToString(List<Integer> array) {
+        return "[" + array.stream()
+                .map(String::valueOf)
+                .collect(Collectors.joining(", ")) + "]";
+    }
+}
diff --git a/src/test/java/domain/engine/LottoMachineTest.java b/src/test/java/domain/engine/LottoMachineTest.java
new file mode 100644
index 00000000000..2a24f8ba6d5
--- /dev/null
+++ b/src/test/java/domain/engine/LottoMachineTest.java
@@ -0,0 +1,25 @@
+package domain.engine;
+
+import domain.model.LottoNumbers;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import domain.generator.StaticLottoNumberGenerator;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LottoMachineTest {
+    @DisplayName("구입 금액에 따라 로또를 구매한다.")
+    @Test
+    void buyTest() {
+        List<Integer> lottoNumbers = List.of(1, 2, 3, 4, 5, 6);
+        LottoMachine lottoMachine = new LottoMachine(new StaticLottoNumberGenerator(lottoNumbers));
+
+        List<LottoNumbers> lottoList = lottoMachine.buyLottos(2000);
+        assertThat(lottoList).hasSize(2);
+        assertThat(lottoList.get(0).getNumbers())
+                .containsExactlyInAnyOrder(lottoNumbers.toArray(new Integer[0]));
+
+    }
+}
diff --git a/src/test/java/domain/generator/StaticLottoNumberGenerator.java b/src/test/java/domain/generator/StaticLottoNumberGenerator.java
new file mode 100644
index 00000000000..30bbf917ebe
--- /dev/null
+++ b/src/test/java/domain/generator/StaticLottoNumberGenerator.java
@@ -0,0 +1,17 @@
+package domain.generator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StaticLottoNumberGenerator implements LottoNumberGenerator {
+    private final List<Integer> lottoNumbers;
+
+    public StaticLottoNumberGenerator(List<Integer> lottoNumbers) {
+        this.lottoNumbers = lottoNumbers;
+    }
+
+    @Override
+    public List<Integer> generate()  {
+        return new ArrayList<>(this.lottoNumbers);
+    }
+}
diff --git a/src/test/java/domain/model/LottoNumbersTest.java b/src/test/java/domain/model/LottoNumbersTest.java
new file mode 100644
index 00000000000..418afe8b588
--- /dev/null
+++ b/src/test/java/domain/model/LottoNumbersTest.java
@@ -0,0 +1,59 @@
+package domain.model;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LottoNumbersTest {
+    @DisplayName("로또 번호를 생성한다.")
+    @Test
+    void createTest() {
+        LottoNumbers lottoNumbers = new LottoNumbers(List.of(1, 2, 3, 4, 5, 6));
+
+        assertThat(lottoNumbers.getNumbers()).containsExactlyInAnyOrder(1, 2, 3, 4, 5, 6);
+    }
+
+    @DisplayName("중복이 있는 로또 번호를 생성할 수 없다.")
+    @Test
+    void createDuplicateTest() {
+        try {
+            new LottoNumbers(List.of(1, 2, 3, 4, 5, 5));
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage()).isEqualTo("Lotto numbers must not contain duplicates.");
+        }
+    }
+
+    @DisplayName("로또 번호의 개수가 6개가 아니면 예외를 발생시킨다.")
+    @Test
+    void createSizeTest() {
+        try {
+            new LottoNumbers(List.of(1, 2, 3, 4, 5));
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage()).isEqualTo("Lotto numbers must contain exactly 6 numbers.");
+        }
+    }
+
+    @DisplayName("로또 번호의 범위가 1~45가 아니면 예외를 발생시킨다.")
+    @Test
+    void createRangeTest() {
+        try {
+            new LottoNumbers(List.of(0, 2, 3, 4, 5, 6));
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage()).isEqualTo("Lotto numbers must be between 1 and 45.");
+        }
+    }
+
+    @DisplayName("로또 번호를 비교하여 일치하는 개수를 센다.")
+    @Test
+    void countMatchTest() {
+        LottoNumbers lottoNumbers = new LottoNumbers(List.of(1, 2, 3, 4, 5, 6));
+        LottoNumbers winningNumbers = new LottoNumbers(List.of(1, 2, 3, 7, 8, 9));
+
+        lottoNumbers.countMatch(winningNumbers);
+
+        assertThat(lottoNumbers.getMatchCount()).isEqualTo(3);
+    }
+}
diff --git a/src/test/java/domain/model/LottoWalletTest.java b/src/test/java/domain/model/LottoWalletTest.java
new file mode 100644
index 00000000000..7f3f1a13eff
--- /dev/null
+++ b/src/test/java/domain/model/LottoWalletTest.java
@@ -0,0 +1,70 @@
+package domain.model;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LottoWalletTest {
+    @DisplayName("로또를 추가한다.")
+    @Test
+    void addTest() {
+        LottoNumbers lotto1 = new LottoNumbers(List.of(1, 2, 3, 4, 5, 6));
+        LottoNumbers lotto2 = new LottoNumbers(List.of(7, 8, 9, 10, 11, 12));
+
+        LottoWallet lottoWallet = new LottoWallet();
+
+        lottoWallet.addLottos(List.of(lotto1, lotto2));
+
+        List<LottoNumbers> lottos = lottoWallet.getLottos();
+        assertThat(lottos).containsExactlyInAnyOrder(lotto1, lotto2);
+    }
+
+    @DisplayName("로또 번호를 비교하여 일치하는 개수를 센다.")
+    @Test
+    void countMatchTest() {
+        LottoNumbers lotto1 = new LottoNumbers(List.of(1, 2, 3, 4, 5, 6));
+        LottoNumbers lotto2 = new LottoNumbers(List.of(7, 8, 9, 10, 11, 12));
+        LottoNumbers winningNumbers = new LottoNumbers(List.of(1, 2, 3, 7, 8, 9));
+
+        LottoWallet lottoWallet = new LottoWallet();
+        lottoWallet.addLottos(List.of(lotto1, lotto2));
+
+        lottoWallet.countMatches(winningNumbers);
+
+        assertThat(lotto1.getMatchCount()).isEqualTo(3);
+        assertThat(lotto2.getMatchCount()).isEqualTo(0);
+    }
+
+    @DisplayName("로또 번호의 일치 결과를 반환한다.")
+    @Test
+    void getMatchResultTest() {
+        LottoNumbers lotto1 = new LottoNumbers(List.of(1, 2, 3, 4, 5, 6));
+        LottoNumbers lotto2 = new LottoNumbers(List.of(7, 8, 9, 10, 11, 12));
+        LottoNumbers winningNumbers = new LottoNumbers(List.of(1, 2, 3, 7, 8, 9));
+
+        LottoWallet lottoWallet = new LottoWallet();
+        lottoWallet.addLottos(List.of(lotto1, lotto2));
+
+        lottoWallet.countMatches(winningNumbers);
+
+        assertThat(lottoWallet.getMatchResult()).containsEntry(3, 1);
+    }
+
+    @DisplayName("로또 번호의 총 당첨 금액을 반환한다.")
+    @Test
+    void getPrizeTest() {
+        LottoNumbers lotto1 = new LottoNumbers(List.of(1, 2, 3, 4, 5, 6));
+        LottoNumbers lotto2 = new LottoNumbers(List.of(7, 8, 9, 10, 11, 12));
+        LottoNumbers winningNumbers = new LottoNumbers(List.of(1, 2, 3, 7, 8, 9));
+
+        LottoWallet lottoWallet = new LottoWallet();
+        lottoWallet.addLottos(List.of(lotto1, lotto2));
+
+        lottoWallet.countMatches(winningNumbers);
+
+        assertThat(lottoWallet.getPrize()).isEqualTo(0); // Assuming no prize for this test case
+    }
+}
diff --git a/src/test/java/CalculatorTest.java b/src/test/java/utils/CalculatorTest.java
similarity index 85%
rename from src/test/java/CalculatorTest.java
rename to src/test/java/utils/CalculatorTest.java
index bc85237b630..591eddd1f73 100644
--- a/src/test/java/CalculatorTest.java
+++ b/src/test/java/utils/CalculatorTest.java
@@ -1,3 +1,5 @@
+package utils;
+
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
@@ -35,12 +37,19 @@ void divideTest() {
         assertEquals(2, result);
     }
 
+    @DisplayName("복합 연산 테스트")
+    @Test
+    void complexOperationTest() {
+        int result = Calculator.run("2 + 3 * 4 / 2");
+        assertEquals(10, result);
+    }
+
     @DisplayName("입력 값이 null이거나 빈 공백 문자일 경우")
     @ParameterizedTest
     @ValueSource(strings = {"", " "})
-    void nullOrEmptyTest() {
+    void nullOrEmptyTest(String input) {
         assertThatIllegalArgumentException().isThrownBy(() -> {
-            Calculator.run(null);
+            Calculator.run(input);
         }).withMessage("입력 값이 null이거나 빈 공백 문자입니다.");
     }
 


Reviews:
user: login: nooose
body: 안녕하세요 보민님
미션 잘 구현해 주셨어요!
컨벤션 관련한 피드백도 같이 남겼으니 확인 부탁드립니다!
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-31T14:11:12Z
user: login: bo-10000
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:59:02Z
user: login: bo-10000
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T08:23:45Z
user: login: bo-10000
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T09:53:32Z
user: login: nooose
body: 보민님 피드백 반영 너무 잘되었네요 👍 
다만 약간 아쉬운 부분이 있어 재요청 눌렀어요
답변과 코멘트 남겼으니 확인 후 재요청 부탁드립니다!
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-01T15:45:20Z
user: login: bo-10000
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-03T11:15:30Z
user: login: nooose
body: 피드백 반영 잘 되었네요! 👍 
코멘트 확인 후 다음 단계 진행해 주세요 
수고하셨습니다!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-03T16:36:13Z

Review comments:
diff_hunk: @@ -0,0 +1,65 @@
+package controller;
+
+import view.InputView;
+import view.ResultView;
+import domain.model.Lotto;
+import domain.model.LottoNumbers;
+import domain.model.LottoWallet;
+import domain.engine.LottoMatchCounter;
+import domain.engine.LottoMachine;
+import domain.generator.RandomLottoNumberGenerator;
+
+import java.util.List;
+import java.util.Map;
+
+
+public class LottoController {
+    private final LottoMachine lottoMachine = new LottoMachine(new RandomLottoNumberGenerator());
+    private final LottoWallet lottoWallet = new LottoWallet();
+    private LottoMatchCounter lottoMatchCounter;
+
+    private void purchase(int purchaseAmount) {
+        List<Lotto> lottoList = this.lottoMachine.buyLottos(purchaseAmount);
+        ResultView.printPurchasedLottoCount(lottoList.size());
+        this.lottoWallet.addLottos(lottoList);
+
+        for (Lotto lotto : this.lottoWallet.getLottos()) {
+            ResultView.printLottoNumbers(lotto.getLottoNumbers());
+        }
+    }
+
+    private LottoNumbers draw() {
+        return new LottoNumbers(InputView.getWinNumbers());
+    }
+
+    private Map<Integer, Integer> announce(LottoNumbers winNumbers) {
+        this.lottoMatchCounter = new LottoMatchCounter(winNumbers);
+        Map<Integer, Integer> matchResult = this.lottoMatchCounter.countMatches(lottoWallet.getLottos());
+        ResultView.printMessage("당첨 통계");
+        ResultView.printMessage("---------");
+        for (int matchCount : matchResult.keySet()) {
+            ResultView.printMatchCount(
+                    matchCount,
+                    LottoMatchCounter.MATCH_PRICES.getOrDefault(matchCount, 0),
+                    matchResult.getOrDefault(matchCount, 0)
+            );
+        }
+        return matchResult;
+    }
+
+    private void calculateProfit(Map<Integer, Integer> matchResult, int purchaseAmount) {
+        double profit = this.lottoMatchCounter.calculateProfit(matchResult, purchaseAmount);
+        ResultView.printMessage(String.format("총 수익률은 %.2f 입니다.", profit));
+    }
+
+    public void run() {
path: src/main/java/controller/LottoController.java
user: login: nooose
body: private 메소드를 위에 선언하는 스타일이네요
코드를 위에서 아래로 내려가면서 읽을 수 있도록 배치해보면 어떨까요?


```java
public void caller() { 
    callee();
}

private void callee() {
    ...
}
```

created_at: 2025-03-31T13:21:54Z
author_association: MEMBER
diff_hunk: @@ -35,12 +37,19 @@ void divideTest() {
         assertEquals(2, result);
     }
 
+    @DisplayName("복합 연산 테스트")
+    @Test
+    void complexOperationTest() {
+        int result = Calculator.run("2 + 3 * 4 / 2");
+        assertEquals(10, result);
+    }
+
     @DisplayName("입력 값이 null이거나 빈 공백 문자일 경우")
     @ParameterizedTest
     @ValueSource(strings = {"", " "})
-    void nullOrEmptyTest() {
+    void nullOrEmptyTest(String input) {
         assertThatIllegalArgumentException().isThrownBy(() -> {
-            Calculator.run(null);
+            Calculator.run(input);
path: src/test/java/utils/CalculatorTest.java
user: login: nooose
body: 이전 피드백 테스트 까지 반영되었네요 👍 👍 
created_at: 2025-03-31T13:22:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,22 @@
+package domain.engine;
+
+import domain.model.Lotto;
+import org.junit.jupiter.api.Test;
+import domain.generator.StaticLottoNumberGenerator;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LottoMachineTest {
path: src/test/java/domain/engine/LottoMachineTest.java
user: login: nooose
body: 테스트 케이스에 `@DisplayName`을 사용해서 테스트 의도를 드러내면 더 좋겠습니다.
그러면 테스트 로직을 확인하지 않고도 대략적으로 파악할 수 있는 장점이 있습니다!
created_at: 2025-03-31T13:24:51Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,19 @@
+package domain.generator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class RandomLottoNumberGenerator implements LottoNumberGenerator {
+    @Override
+    public List<Integer> generate()  {
+        List<Integer> allNumbers = new ArrayList<>();
+        for (int i = 1; i <= 45; i++) {
+            allNumbers.add(i);
+        }
path: src/main/java/domain/generator/RandomLottoNumberGenerator.java
user: login: nooose
body: 1~45개의 수를 매번 만들기보다는 
미리 만들어둔 숫자 풀내에서 번호를 뽑아보면 어떨까요?

```java
Collections.shuffle(CACHED_NUMBERS);
```
created_at: 2025-03-31T13:38:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,19 @@
+package domain.generator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class RandomLottoNumberGenerator implements LottoNumberGenerator {
+    @Override
+    public List<Integer> generate()  {
+        List<Integer> allNumbers = new ArrayList<>();
+        for (int i = 1; i <= 45; i++) {
+            allNumbers.add(i);
+        }
path: src/main/java/domain/generator/RandomLottoNumberGenerator.java
user: login: nooose
body: 1~45개의 수를 매번 만들기보다는 
미리 만들어둔 숫자 풀내에서 번호를 뽑아보면 어떨까요?

```java
Collections.shuffle(CACHED_NUMBERS);
```
created_at: 2025-03-31T13:39:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,15 @@
+package domain.model;
+
+import java.util.List;
+
+public class Lotto {
path: src/main/java/domain/model/Lotto.java
user: login: nooose
body: Lotto와 LottoNumbers는 어떤 차이가 있을까요? 🤔

아마도 일급 컬렉션을 적용해 보신 것 같아요.
그렇다면 단순히 컬렉션을 감싸는 것이 아니라 일급 컬렉션을 통해 얻을 수 있는 이점이 분명해야 한다고 생각해요

Lotto는 LottoNumbers를 래핑하고, LottoNumbers는 로또 번호를 담고 있는데,
각각의 역할이 무엇인지 고민해 보면 더 좋은 설계가 될 것 같아요! 😊

가령 로또는 6개의 수를 가져야하고, 중복이 없어야하는 등등의 책임을 가지게 끔 만들어볼 수 있겠네요!

created_at: 2025-03-31T13:49:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,19 @@
+package domain.generator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class RandomLottoNumberGenerator implements LottoNumberGenerator {
+    @Override
+    public List<Integer> generate()  {
+        List<Integer> allNumbers = new ArrayList<>();
+        for (int i = 1; i <= 45; i++) {
path: src/main/java/domain/generator/RandomLottoNumberGenerator.java
user: login: nooose
body: 1, 45 같은 수도 매직넘버이니 상수로 바꿔보면 어떨까요?
created_at: 2025-03-31T13:53:55Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,26 @@
+package domain.engine;
+
+import domain.generator.LottoNumberGenerator;
+import domain.generator.RandomLottoNumberGenerator;
+import domain.model.Lotto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LottoMachine {
+    private static final int LOTTO_PRICE = 1000;
+    private final LottoNumberGenerator lottoNumberGenerator;
+
+    public List<Lotto> buyLottos(int purchaseAmount) {
+        int numLottos = purchaseAmount / LOTTO_PRICE;
+        List<Lotto> lottos = new ArrayList<>();
+        for (int i = 0; i < numLottos; i++) {
+            lottos.add(new Lotto(this.lottoNumberGenerator.generate()));
+        }
+        return lottos;
+    }
+
+    public LottoMachine(LottoNumberGenerator lottoNumberGenerator) {
+        this.lottoNumberGenerator = lottoNumberGenerator;
+    }
path: src/main/java/domain/engine/LottoMachine.java
user: login: nooose
body: 생성자는 클래스 멤버변수 아래에 배치하는 것이 자바 컨벤션입니다.
협업을 위해 지켜주면 좋을 것 같아요! 😄 
created_at: 2025-03-31T13:54:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,51 @@
+package domain.engine;
+
+import domain.model.LottoNumbers;
+import domain.model.Lotto;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LottoMatchCounter {
+    public static final Map<Integer, Integer> MATCH_PRICES = Map.of(
+            3, 5000,
+            4, 50000,
+            5, 1500000,
+            6, 2000000000
+    );
+    private LottoNumbers winNumbers;
+
+    public LottoMatchCounter(LottoNumbers winNumbers) {
+        this.winNumbers = winNumbers;
+    }
+
+    public Map<Integer, Integer> countMatches(List<Lotto> lottos) {
path: src/main/java/domain/engine/LottoMatchCounter.java
user: login: nooose
body: 등수와 등수 개수를 맵으로 제공하고 있네요
이러면 위 메소드를 사용하는 입장에서는 key, value 모두 정수이기에 어떤 값인지 난해합니다. 😰 
별도의 객체를 반환시켜 결과를 명확하게 나타내면 어떨까요?
created_at: 2025-03-31T13:56:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,51 @@
+package domain.engine;
+
+import domain.model.LottoNumbers;
+import domain.model.Lotto;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LottoMatchCounter {
+    public static final Map<Integer, Integer> MATCH_PRICES = Map.of(
+            3, 5000,
+            4, 50000,
+            5, 1500000,
+            6, 2000000000
+    );
+    private LottoNumbers winNumbers;
+
+    public LottoMatchCounter(LottoNumbers winNumbers) {
+        this.winNumbers = winNumbers;
+    }
+
+    public Map<Integer, Integer> countMatches(List<Lotto> lottos) {
+        Map<Integer, Integer> matchResult = new HashMap<>();
+        for (Lotto lotto : lottos) {
+            int matchCount = countMatch(lotto);
+            if (MATCH_PRICES.containsKey(matchCount)) {
+                matchResult.put(matchCount, matchResult.getOrDefault(matchCount, 0) + 1);
+            }
+        }
+        return matchResult;
+    }
+
+    private int countMatch(Lotto lotto) {
+        int matchCount = 0;
+        for (int number : lotto.getLottoNumbers()) {
+            if (this.winNumbers.getNumbers().contains(number)) {
+                matchCount++;
+            }
+        }
path: src/main/java/domain/engine/LottoMatchCounter.java
user: login: nooose
body: > indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.

요구사항을 지켜서 depth를 줄여보면 어떨까요?
created_at: 2025-03-31T14:03:51Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,51 @@
+package domain.engine;
+
+import domain.model.LottoNumbers;
+import domain.model.Lotto;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LottoMatchCounter {
+    public static final Map<Integer, Integer> MATCH_PRICES = Map.of(
+            3, 5000,
+            4, 50000,
+            5, 1500000,
+            6, 2000000000
+    );
+    private LottoNumbers winNumbers;
+
+    public LottoMatchCounter(LottoNumbers winNumbers) {
+        this.winNumbers = winNumbers;
+    }
+
+    public Map<Integer, Integer> countMatches(List<Lotto> lottos) {
+        Map<Integer, Integer> matchResult = new HashMap<>();
+        for (Lotto lotto : lottos) {
+            int matchCount = countMatch(lotto);
+            if (MATCH_PRICES.containsKey(matchCount)) {
+                matchResult.put(matchCount, matchResult.getOrDefault(matchCount, 0) + 1);
+            }
+        }
+        return matchResult;
+    }
+
+    private int countMatch(Lotto lotto) {
+        int matchCount = 0;
+        for (int number : lotto.getLottoNumbers()) {
+            if (this.winNumbers.getNumbers().contains(number)) {
path: src/main/java/domain/engine/LottoMatchCounter.java
user: login: nooose
body: winNumbers에게 메시지를 보내면 어떨까요?
`winNumbers.contains(number)`
created_at: 2025-03-31T14:04:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,28 @@
+package view;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ResultView {
+    public static void printPurchasedLottoCount(int count) {
+        printMessage(count + "개를 구매했습니다.");
+    }
+
+    public static void printLottoNumbers(List<Integer> lottoNumbers) {
+        printMessage(arrayToString(lottoNumbers));
+    }
+
+    public static void printMatchCount(int matchCount, int price, int count) {
+        printMessage(matchCount + "개 일치 (" + price + "원)- " + count + "개");
+    }
+
+    private static String arrayToString(List<Integer> array) {
+        return "[" + array.stream()
+                .map(String::valueOf)
+                .collect(Collectors.joining(", ")) + "]";
+    }
path: src/main/java/view/ResultView.java
user: login: nooose
body: 출력 책임이 잘 나눠져있네요 👍 👍 
created_at: 2025-03-31T14:07:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,17 @@
+package domain.generator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StaticLottoNumberGenerator implements LottoNumberGenerator {
path: src/test/java/domain/generator/StaticLottoNumberGenerator.java
user: login: nooose
body: 인터페이스 장점이 잘 드러나는 구조네요! 👍 👍 
created_at: 2025-03-31T14:07:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,65 @@
+package controller;
+
+import view.InputView;
+import view.ResultView;
+import domain.model.Lotto;
+import domain.model.LottoNumbers;
+import domain.model.LottoWallet;
+import domain.engine.LottoMatchCounter;
+import domain.engine.LottoMachine;
+import domain.generator.RandomLottoNumberGenerator;
+
+import java.util.List;
+import java.util.Map;
+
+
+public class LottoController {
+    private final LottoMachine lottoMachine = new LottoMachine(new RandomLottoNumberGenerator());
+    private final LottoWallet lottoWallet = new LottoWallet();
+    private LottoMatchCounter lottoMatchCounter;
+
+    private void purchase(int purchaseAmount) {
+        List<Lotto> lottoList = this.lottoMachine.buyLottos(purchaseAmount);
+        ResultView.printPurchasedLottoCount(lottoList.size());
+        this.lottoWallet.addLottos(lottoList);
+
+        for (Lotto lotto : this.lottoWallet.getLottos()) {
+            ResultView.printLottoNumbers(lotto.getLottoNumbers());
+        }
+    }
+
+    private LottoNumbers draw() {
+        return new LottoNumbers(InputView.getWinNumbers());
+    }
+
+    private Map<Integer, Integer> announce(LottoNumbers winNumbers) {
+        this.lottoMatchCounter = new LottoMatchCounter(winNumbers);
+        Map<Integer, Integer> matchResult = this.lottoMatchCounter.countMatches(lottoWallet.getLottos());
+        ResultView.printMessage("당첨 통계");
+        ResultView.printMessage("---------");
+        for (int matchCount : matchResult.keySet()) {
+            ResultView.printMatchCount(
+                    matchCount,
+                    LottoMatchCounter.MATCH_PRICES.getOrDefault(matchCount, 0),
+                    matchResult.getOrDefault(matchCount, 0)
+            );
+        }
path: src/main/java/controller/LottoController.java
user: login: nooose
body: ResultView를 단순 콘솔 출력용으로만 사용하는 대신 출력의 흐름을 맡겨보면 어떨까요?

```java
ResultView.printResult(matchResult);
```
created_at: 2025-03-31T14:09:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,15 @@
+package domain.model;
+
+import java.util.List;
+
+public class Lotto {
path: src/main/java/domain/model/Lotto.java
user: login: bo-10000
body: LottoNumbers와 Lotto의 역할에 차이가 없는것 같아 Lotto를 LottoNumbers로 대체했습니다.
created_at: 2025-04-01T07:59:02Z
author_association: NONE
diff_hunk: @@ -0,0 +1,19 @@
+package domain.generator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class RandomLottoNumberGenerator implements LottoNumberGenerator {
+    @Override
+    public List<Integer> generate()  {
+        List<Integer> allNumbers = new ArrayList<>();
+        for (int i = 1; i <= 45; i++) {
path: src/main/java/domain/generator/RandomLottoNumberGenerator.java
user: login: bo-10000
body: domanin.constant 밑에 LottoConstants 클래스를 만들어 반복적으로 사용되는 상수들을 분리했습니다.
created_at: 2025-04-01T08:23:44Z
author_association: NONE
diff_hunk: @@ -0,0 +1,51 @@
+package domain.engine;
+
+import domain.model.LottoNumbers;
+import domain.model.Lotto;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LottoMatchCounter {
+    public static final Map<Integer, Integer> MATCH_PRICES = Map.of(
+            3, 5000,
+            4, 50000,
+            5, 1500000,
+            6, 2000000000
+    );
+    private LottoNumbers winNumbers;
+
+    public LottoMatchCounter(LottoNumbers winNumbers) {
+        this.winNumbers = winNumbers;
+    }
+
+    public Map<Integer, Integer> countMatches(List<Lotto> lottos) {
+        Map<Integer, Integer> matchResult = new HashMap<>();
+        for (Lotto lotto : lottos) {
+            int matchCount = countMatch(lotto);
+            if (MATCH_PRICES.containsKey(matchCount)) {
+                matchResult.put(matchCount, matchResult.getOrDefault(matchCount, 0) + 1);
+            }
+        }
+        return matchResult;
+    }
+
+    private int countMatch(Lotto lotto) {
+        int matchCount = 0;
+        for (int number : lotto.getLottoNumbers()) {
+            if (this.winNumbers.getNumbers().contains(number)) {
path: src/main/java/domain/engine/LottoMatchCounter.java
user: login: bo-10000
body: countMatch로 구현했습니다.
created_at: 2025-04-01T09:53:32Z
author_association: NONE
diff_hunk: @@ -0,0 +1,45 @@
+package domain.model;
+
+import java.util.List;
+
+import static domain.constant.LottoConstants.*;
+
+public class LottoNumbers {
+    private final List<Integer> numbers;
+    private int matchCount;
+
+    public LottoNumbers(List<Integer> numbers) {
+        this.numbers = numbers;
+        validate();
+    }
+
+    public List<Integer> getNumbers() {
+        return this.numbers;
+    }
+
+    public void countMatch(LottoNumbers winNumbers) {
+        for (int number : winNumbers.getNumbers()) {
+            if (this.numbers.contains(number)) {
+                this.matchCount++;
+            }
+        }
+    }
path: src/main/java/domain/model/LottoNumbers.java
user: login: nooose
body: 카운팅된 수를 멤버변수 상태로 가지고 있을 필요가 있을까요?
해당 메소드 스코프 내에서 계산 후 반환하면 어떨까요?

지금 상태는 사이드 이펙트가 존재합니다.
```java
lottoNumbers.countMatch(winNumbers);
lottoNumbers.countMatch(winNumbers);
lottoNumbers.countMatch(winNumbers);

// 3번 호출했을 때 matchCount 값은?
```
n번 호출하여도 항상 같은 값을 반환할 수 있도록 멱등성있게 만들어보면 좋겠습니다.


created_at: 2025-04-01T15:25:27Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,45 @@
+package domain.model;
+
+import java.util.List;
+
+import static domain.constant.LottoConstants.*;
+
+public class LottoNumbers {
+    private final List<Integer> numbers;
+    private int matchCount;
+
+    public LottoNumbers(List<Integer> numbers) {
+        this.numbers = numbers;
+        validate();
+    }
+
+    public List<Integer> getNumbers() {
+        return this.numbers;
+    }
+
+    public void countMatch(LottoNumbers winNumbers) {
+        for (int number : winNumbers.getNumbers()) {
+            if (this.numbers.contains(number)) {
+                this.matchCount++;
+            }
+        }
+    }
+
+    int getMatchCount() {
+        return this.matchCount;
+    }
+
+    private void validate() {
+        if (numbers.size() != LOTTO_NUMBER_COUNT) {
+            throw new IllegalArgumentException("Lotto numbers must contain exactly 6 numbers.");
+        }
+        for (Integer number : numbers) {
+            if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
+                throw new IllegalArgumentException("Lotto numbers must be between 1 and 45.");
+            }
+        }
path: src/main/java/domain/model/LottoNumbers.java
user: login: nooose
body: 이 부분도 depth를 줄여보면 좋겠습니다!

> indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.


created_at: 2025-04-01T15:26:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,47 @@
+package domain.model;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static domain.constant.LottoConstants.MATCH_PRICES;
+
+public class LottoWallet {
+    private final List<LottoNumbers> lottos = new ArrayList<>();
+
+    public void addLottos(List<LottoNumbers> lottos) {
+        this.lottos.addAll(lottos);
+    }
+
+    public void countMatches(LottoNumbers winNumbers) {
+        for (LottoNumbers lotto : lottos) {
+            lotto.countMatch(winNumbers);
+        }
+    }
+
+    public Map<Integer, Integer> getMatchResult() {
+        Map<Integer, Integer> matchResult = new HashMap<>();
+        for (LottoNumbers lotto : lottos) {
+            int matchCount = lotto.getMatchCount();
+            if (MATCH_PRICES.containsKey(matchCount)) {
+                matchResult.put(matchCount, matchResult.getOrDefault(matchCount, 0) + 1);
+            }
+        }
path: src/main/java/domain/model/LottoWallet.java
user: login: nooose
body: > indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.

🥲 
created_at: 2025-04-01T15:26:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,15 @@
+package domain.constant;
+
+import java.util.Map;
+
+public class LottoConstants {
+    public static final int LOTTO_NUMBER_COUNT = 6;
+    public static final int MIN_LOTTO_NUMBER = 1;
+    public static final int MAX_LOTTO_NUMBER = 45;
+    public static final Map<Integer, Integer> MATCH_PRICES = Map.of(
+            3, 5000,
+            4, 50000,
+            5, 1500000,
+            6, 2000000000
path: src/main/java/domain/constant/LottoConstants.java
user: login: nooose
body: ```suggestion
            3, 5_000,
            4, 50_000,
            5, 1_500_000,
            6, 2_000_000_000
```
천단위로 표현이 가능합니다!
created_at: 2025-04-01T15:27:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,15 @@
+package domain.constant;
+
+import java.util.Map;
+
+public class LottoConstants {
+    public static final int LOTTO_NUMBER_COUNT = 6;
+    public static final int MIN_LOTTO_NUMBER = 1;
+    public static final int MAX_LOTTO_NUMBER = 45;
+    public static final Map<Integer, Integer> MATCH_PRICES = Map.of(
path: src/main/java/domain/constant/LottoConstants.java
user: login: nooose
body: 상수를 모아두셨네요 👍 

created_at: 2025-04-01T15:30:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,25 @@
+package domain.engine;
+
+import domain.generator.LottoNumberGenerator;
+import domain.model.LottoNumbers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LottoMachine {
+    private static final int LOTTO_PRICE = 1000;
+    private final LottoNumberGenerator lottoNumberGenerator;
path: src/main/java/domain/engine/LottoMachine.java
user: login: nooose
body: ```suggestion
    private static final int LOTTO_PRICE = 1000;
    
    private final LottoNumberGenerator lottoNumberGenerator;
```

상수와 멤버변수 간에 개행으로 구분지으면 어떨까요?
created_at: 2025-04-01T15:31:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,46 @@
+package controller;
+
+import view.InputView;
+import view.ResultView;
+import domain.model.LottoNumbers;
+import domain.model.LottoWallet;
+import domain.engine.LottoMachine;
+import domain.generator.RandomLottoNumberGenerator;
+
+import java.util.List;
+
+
+public class LottoController {
+    private final LottoWallet lottoWallet = new LottoWallet();
+    private final LottoMachine lottoMachine = new LottoMachine(new RandomLottoNumberGenerator());
+
+    public void run() {
+        int purchaseAmount = InputView.getPurchaseAmount();
+        purchase(purchaseAmount);
+
+        draw();
+
+        calculateProfit(purchaseAmount);
+    }
+
+    private void purchase(int purchaseAmount) {
+        List<LottoNumbers> lottoList = this.lottoMachine.buyLottos(purchaseAmount);
+        ResultView.printPurchasedLottoCount(lottoList.size());
+        this.lottoWallet.addLottos(lottoList);
+
+        for (LottoNumbers lotto : this.lottoWallet.getLottos()) {
+            ResultView.printLottoNumbers(lotto.getNumbers());
+        }
path: src/main/java/controller/LottoController.java
user: login: nooose
body: `ResultView`를 반복하여 호출하는 대신

`ResultView`에게 `lottoWallet`을 출력해달라고 메시지를 보내보면 어떨까요?

```suggestion
        ResultView.printLottoNumbers(lottoWallet);
```

created_at: 2025-04-01T15:34:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,46 @@
+package controller;
+
+import view.InputView;
+import view.ResultView;
+import domain.model.LottoNumbers;
+import domain.model.LottoWallet;
+import domain.engine.LottoMachine;
+import domain.generator.RandomLottoNumberGenerator;
+
+import java.util.List;
+
+
+public class LottoController {
+    private final LottoWallet lottoWallet = new LottoWallet();
+    private final LottoMachine lottoMachine = new LottoMachine(new RandomLottoNumberGenerator());
+
+    public void run() {
+        int purchaseAmount = InputView.getPurchaseAmount();
+        purchase(purchaseAmount);
+
+        draw();
+
+        calculateProfit(purchaseAmount);
+    }
+
+    private void purchase(int purchaseAmount) {
+        List<LottoNumbers> lottoList = this.lottoMachine.buyLottos(purchaseAmount);
+        ResultView.printPurchasedLottoCount(lottoList.size());
+        this.lottoWallet.addLottos(lottoList);
+
+        for (LottoNumbers lotto : this.lottoWallet.getLottos()) {
+            ResultView.printLottoNumbers(lotto.getNumbers());
+        }
path: src/main/java/controller/LottoController.java
user: login: bo-10000
body: view에 도메인 객체에 대한 의존성이 들어가면 안된다고 생각해서 for문을 돌며 numbers만 넘기는 방식으로 구현했는데요, 반드시 그럴 필요는 없는것인지 질문드리고 싶습니다!
created_at: 2025-04-03T11:15:30Z
author_association: NONE
diff_hunk: @@ -0,0 +1,33 @@
+package domain.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static constant.LottoConstants.MATCH_PRICES;
+
+public class MatchResult {
+    private Map<Integer, Integer> resultMap = new HashMap<>();
path: src/main/java/domain/model/MatchResult.java
user: login: nooose
body: ```suggestion
    private final Map<Integer, Integer> resultMap = new HashMap<>();
```
resultMap에 할당할 일이 더 이상 없다면 final을 붙여 불변을 유지하면 좋겠습니다!
created_at: 2025-04-03T16:24:16Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,50 @@
+package domain.model;
+
+import java.util.List;
+
+import static constant.LottoConstants.*;
+
+public class LottoNumbers {
+    private final List<Integer> numbers;
+
+    public LottoNumbers(List<Integer> numbers) {
+        this.numbers = numbers;
+        validate();
+    }
path: src/main/java/domain/model/LottoNumbers.java
user: login: nooose
body: 호출되는 메소드들을 가까이 배치해 봐도 좋겠어요

```java
    public LottoNumbers(List<Integer> numbers) {
        this.numbers = numbers;
        validate();
    }
     
     private void validate() {
        validateSize();
        validateRange();
        validateNoDuplicates();
    }

    private void validate() { ... }
```
created_at: 2025-04-03T16:25:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,29 @@
+package domain.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class LottoWallet {
+    private final List<LottoNumbers> lottos = new ArrayList<>();
+
+    public void addLotto(LottoNumbers lotto) {
+        this.lottos.add(lotto);
+    }
+
+    public MatchResult countMatches(LottoNumbers winNumbers) {
+        MatchResult matchResult = new MatchResult();
+        for (LottoNumbers lotto : lottos) {
+            matchResult.addMatchCount(lotto.countMatch(winNumbers));
+        }
+        return matchResult;
+    }
path: src/main/java/domain/model/LottoWallet.java
user: login: nooose
body: 👍 👍 
created_at: 2025-04-03T16:26:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,46 @@
+package controller;
+
+import view.InputView;
+import view.ResultView;
+import domain.model.LottoNumbers;
+import domain.model.LottoWallet;
+import domain.engine.LottoMachine;
+import domain.generator.RandomLottoNumberGenerator;
+
+import java.util.List;
+
+
+public class LottoController {
+    private final LottoWallet lottoWallet = new LottoWallet();
+    private final LottoMachine lottoMachine = new LottoMachine(new RandomLottoNumberGenerator());
+
+    public void run() {
+        int purchaseAmount = InputView.getPurchaseAmount();
+        purchase(purchaseAmount);
+
+        draw();
+
+        calculateProfit(purchaseAmount);
+    }
+
+    private void purchase(int purchaseAmount) {
+        List<LottoNumbers> lottoList = this.lottoMachine.buyLottos(purchaseAmount);
+        ResultView.printPurchasedLottoCount(lottoList.size());
+        this.lottoWallet.addLottos(lottoList);
+
+        for (LottoNumbers lotto : this.lottoWallet.getLottos()) {
+            ResultView.printLottoNumbers(lotto.getNumbers());
+        }
path: src/main/java/controller/LottoController.java
user: login: nooose
body: 뷰에서 도메인 객체를 직접 참조하는 것이 반드시 나쁜 것은 아닙니다.
핵심은 의존성이 단방향으로 흐르는가입니다. 

view는 출력의 책임을 갖고 있기에
비즈니스 로직의 구현을 피하고 출력 로직에만 집중하면 됩니다.

- 로또를 어떻게 표현하지? --> view에게 맡긴다.
  - 로또를 JSON 데이터로 표현하자 --> view에게 맡긴다.
  - 로또를 단순 텍스트로 표현하자 --> view에게 맡긴다.

가령 회원 목록 json 배열을 Front-End에 응답하면 
Front-End에서 for문을 돌려 여러 사용자를 예쁘게 렌더링해 보여주는 것도 Front-End의 책임인 것 처럼요
created_at: 2025-04-03T16:35:26Z
author_association: MEMBER

PR comments:
user: login: bo-10000
created_at: 2025-04-01T09:55:04Z
body: 리뷰주신 내용 반영했습니다! 변경사항이 좀 많네요 ㅎㅎ
고민되는 부분이 하나 있는데, LottoWallet.getMatchResult() 를 통해 매치 결과를 계산하고, ResultView에 전달해 결과를 출력하게 하려 했는데
map이 아닌 어떤 형식으로 전달할 수 있을지 잘 모르겠네요..
author_association: NONE
user: login: nooose
created_at: 2025-04-01T15:42:35Z
body: > 고민되는 부분이 하나 있는데, LottoWallet.getMatchResult() 를 통해 매치 결과를 계산하고, ResultView에 전달해 결과를 출력하게 하려 했는데

model 패키지내에 `LottoNumbers`, `LottoWallet` 을 역할별로 잘 나눠서 만드신 것 처럼
로또 결과가 담겨진 `로또 결과 모델`을 설계해보면 되지 않을까요?

`getMatchResult` 호출 시 Map 자료구조 대신 `로또 결과 모델`을 반환하면 좋겠습니다. 

`로또 결과 모델` 객체를 통해 1등이 몇개 당첨되었는지, 수익률은 얼마인지 구할 수 있을 것 같네요
혹시라도 제 답변이 부족했다면 추가 코멘트 남겨주세요!
author_association: MEMBER