Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mvc 연습 #2135

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package lotto;

import lotto.config.AppConfig;
import lotto.controller.LottoController;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
AppConfig appConfig = new AppConfig();
LottoController lottoController = appConfig.lottoController();
lottoController.run();
}
}
20 changes: 0 additions & 20 deletions src/main/java/lotto/Lotto.java

This file was deleted.

15 changes: 15 additions & 0 deletions src/main/java/lotto/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package lotto.config;

import lotto.controller.LottoController;
import lotto.service.LottoService;
import lotto.service.LottoServiceImpl;

public class AppConfig {
public LottoService lottoService(){
return new LottoServiceImpl();
};

public LottoController lottoController(){
return new LottoController(lottoService());
};
}
46 changes: 46 additions & 0 deletions src/main/java/lotto/controller/LottoController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package lotto.controller;

import lotto.dto.LottoDto;
import lotto.model.Lotto;
import lotto.service.LottoService;
import lotto.view.InputView;
import lotto.view.OutputView;

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

public class LottoController {
private final LottoService lottoService;

public LottoController(LottoService lottoService) {
this.lottoService = lottoService;
}
Comment on lines +13 to +17
Copy link

@tiemo0708 tiemo0708 Jan 14, 2025

Choose a reason for hiding this comment

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

컨트롤러는 서비스 뿐만 아니라 view또한 외부 주입 받아야 하나 현재 의존성 주입이 안되고 있으
그냥 static 으로 열어버리는걸 지양해야해!


public void run(){
int purchaseAmount = InputView.getPurchaseAmount();
int numberOfLottos = purchaseAmount/1000;

Choose a reason for hiding this comment

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

컨트롤러에서 계산 로직이 있는것은 지양하는게 좋습니다

Lotto winningLotto = null;

List<List<Integer>> lottoNumbersList = lottoService.generateLottoNumbers(numberOfLottos);
OutputView.printPurchaseCount(numberOfLottos);
OutputView.printLottoNumers(lottoNumbersList);

while(true) {
LottoDto winningNumbersDto = InputView.getWinningNumbers();
try {
winningLotto = new Lotto(winningNumbersDto.getNumbers());
break;
} catch (IllegalArgumentException e) {
System.out.println("[ERROR] " + e.getMessage());
}
}
Comment on lines +28 to +36

Choose a reason for hiding this comment

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

트라이 캐치를 컨트롤러에서만 하면돼 여기서 입력 검증을 할 수 있는 클래스를 호출하고 다음에 도메인 모델 검증을 하게 한다면 컨트롤러 내부에서만 재입력 처리를 해주면 돼


int bonusNumber = InputView.getBonusNumber();

Map<String, Integer> statistics = lottoService.calculateLotto(lottoNumbersList, winningLotto, bonusNumber);
OutputView.printStatistics(statistics);

double profitRate = lottoService.calculateProfitRate(purchaseAmount, statistics);
OutputView.printProfitRate(profitRate);
}
}
15 changes: 15 additions & 0 deletions src/main/java/lotto/dto/LottoDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package lotto.dto;

import java.util.List;

public class LottoDto {

Choose a reason for hiding this comment

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

dto는 데이터가 이동할때 한번 감싸서 넘겨줘 이것은 나중에 할건데
엔티티의 정보를 그대로 넘기면 외부 노출될수도 있고 그래서도 있고 필요한 특정 포맷에 맞출 수 있기도 해 이것말고도 찾아보면 여러 이유들이 있는데 해당 프로젝트에 맞춰서 어느부분에 dto를 사용할지 고려 하여 사용하면 될것 같아

private final List<Integer> numbers;

public LottoDto(List<Integer> numbers) {
this.numbers = numbers;
}

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

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Lotto {
private final List<Integer> numbers;

public Lotto(List<Integer> numbers) {
validate(numbers);
this.numbers = numbers;
}

private void validate(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
}
for (int num : numbers) {
if (num < 1 || num > 45) {
throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
}
Set<Integer> uniqueNumbers = new HashSet<>(numbers);
if (uniqueNumbers.size() != numbers.size()) {
throw new IllegalArgumentException("로또 번호에는 중복이 존재하지 않아야 합니다.");
}
}

public List<Integer> getNumbers() {
return numbers;
}
}
12 changes: 12 additions & 0 deletions src/main/java/lotto/service/LottoService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package lotto.service;

import lotto.model.Lotto;

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

public interface LottoService {
List<List<Integer>> generateLottoNumbers(int numberOfLottos);
Map<String, Integer> calculateLotto(List<List<Integer>> lottoNumbers, Lotto winningLotto, int bonusNumber);
double calculateProfitRate(int purchaseAmount, Map<String, Integer> lottoNumberMap);
}
77 changes: 77 additions & 0 deletions src/main/java/lotto/service/LottoServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package lotto.service;

import camp.nextstep.edu.missionutils.Randoms;
import lotto.model.Lotto;

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

/* 실제 LottoService 기능 구현*/
public class LottoServiceImpl implements LottoService {
@Override
public List<List<Integer>> generateLottoNumbers(int numberOfLottos) {
List<List<Integer>> lottoNumbersList = new ArrayList<>();
for (int i = 0; i < numberOfLottos; i++) {
List<Integer> lottoNumbers = Randoms.pickUniqueNumbersInRange(1, 45, 6);
lottoNumbers.sort(Integer::compareTo);
lottoNumbersList.add(lottoNumbers);
Comment on lines +17 to +19

Choose a reason for hiding this comment

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

불변 객체를 sorted 하고 있어서 문제가 발생하고 있음
실제로는 pickUniqueNumbersInRange로 값이 만들어지지만 테스트 상황에서는 임의 값을 넣어주고 있기 때문

Suggested change
List<Integer> lottoNumbers = Randoms.pickUniqueNumbersInRange(1, 45, 6);
lottoNumbers.sort(Integer::compareTo);
lottoNumbersList.add(lottoNumbers);
List<Integer> mutableNumbers = new ArrayList<>(lottoNumbers);
mutableNumbers.sort(Integer::compareTo);
lottoNumbersList.add(mutableNumbers);

불변 객체를 가변 객체로 만들어 주면 통과~

}
return lottoNumbersList;
}

@Override
public Map<String, Integer> calculateLotto(List<List<Integer>> lottoNumbers, Lotto winningLotto, int bonusNumber){
Map<String, Integer> lottoNumbersMap = new HashMap<>();
lottoNumbersMap.put("3개 일치", 0);
lottoNumbersMap.put("4개 일치", 0);
lottoNumbersMap.put("5개 일치", 0);
lottoNumbersMap.put("5개 일치, 보너스 볼 일치", 0);
lottoNumbersMap.put("6개 일치", 0);

for (List<Integer> lottoNumber : lottoNumbers) {
int matchCount = getMatchCount(lottoNumber, winningLotto.getNumbers());
boolean bonusMatch = lottoNumber.contains(bonusNumber);

if (matchCount == 6) {
lottoNumbersMap.put("6개 일치", lottoNumbersMap.get("6개 일치") + 1);
} else if (matchCount == 5 && bonusMatch) {
lottoNumbersMap.put("5개 일치, 보너스 볼 일치", lottoNumbersMap.get("5개 일치, 보너스 볼 일치") + 1);
} else if (matchCount == 5) {
lottoNumbersMap.put("5개 일치", lottoNumbersMap.get("5개 일치") + 1);
} else if (matchCount == 4) {
lottoNumbersMap.put("4개 일치", lottoNumbersMap.get("4개 일치") + 1);
} else if (matchCount == 3) {
lottoNumbersMap.put("3개 일치", lottoNumbersMap.get("3개 일치") + 1);
}
Comment on lines +37 to +47

Choose a reason for hiding this comment

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

미션 요구 사항에 있는거긴 한데 else if를 최대한 지양 하여야해

}
return lottoNumbersMap;
Comment on lines +37 to +49

Choose a reason for hiding this comment

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

해당 부분 같은경우에는 이넘 클래스를 이용하여 관리 하는게 효과적이야

Suggested change
if (matchCount == 6) {
lottoNumbersMap.put("6개 일치", lottoNumbersMap.get("6개 일치") + 1);
} else if (matchCount == 5 && bonusMatch) {
lottoNumbersMap.put("5개 일치, 보너스 볼 일치", lottoNumbersMap.get("5개 일치, 보너스 볼 일치") + 1);
} else if (matchCount == 5) {
lottoNumbersMap.put("5개 일치", lottoNumbersMap.get("5개 일치") + 1);
} else if (matchCount == 4) {
lottoNumbersMap.put("4개 일치", lottoNumbersMap.get("4개 일치") + 1);
} else if (matchCount == 3) {
lottoNumbersMap.put("3개 일치", lottoNumbersMap.get("3개 일치") + 1);
}
}
return lottoNumbersMap;
public enum LottoRank {
FIRST(6, false, 2_000_000_000, "6개 일치"),
SECOND(5, true, 30_000_000, "5개 일치, 보너스 볼 일치"),
THIRD(5, false, 1_500_000, "5개 일치"),
FOURTH(4, false, 50_000, "4개 일치"),
FIFTH(3, false, 5_000, "3개 일치"),
NONE(0, false, 0, "");
private final int matchCount;
private final boolean matchBonus;
private final int prize;
private final String description;

}
Comment on lines +25 to +50

Choose a reason for hiding this comment

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

해당 내용은 도메인 로직으로 이동 시켜야 할것 같아


private int getMatchCount(List<Integer> lottoNumber, List<Integer> winningNumbers) {
int matchCount = 0;
for (int number : lottoNumber) {
if(winningNumbers.contains(number)) matchCount++;
}
return matchCount;
}

@Override
public double calculateProfitRate(int purchaseAmount, Map<String, Integer> lottoNumberMap) {
int totalWinningAmount = calculateTotalWinningAmount(lottoNumberMap);
return (double) totalWinningAmount / purchaseAmount;
}

Comment on lines +60 to +65

Choose a reason for hiding this comment

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

수익률 계산이 잘못 되고 있어 전체 수악/사용 금액 * 100 이 되어야 수익률이 정상적으로 작동돼

또한 수익률을 담당하는 도메인 클래스로 따로 분리하는게 좋아보여 서비스 로직과 도메인 로직의 차이를 이해하는게 좋을것 같아 서비스 로직에서는 도메인 로직과 애플리케이션 계층 간의 조정 및 흐름 제어를 처리하는곳으로 나중에 디비 까지 쓴다면
도메인 로직을 호출하고, 이를 다른 계층(컨트롤러, 데이터베이스)과 연결 하는 역할을 해주어야해 직접적으로 값이 변하는 이러한 것들 즉 애플리케이션의 핵심 비즈니스 규칙은 도메인 모델 내부에서 이루저 져야해 객체지향적 코드에서는 도메인 모델이 자신의 값을 스스로 수정할 수 있어야해

private int calculateTotalWinningAmount(Map<String, Integer> statistics) {
int totalAmount = 0;

totalAmount += statistics.get("3개 일치") * 5000;
totalAmount += statistics.get("4개 일치") * 50000;
totalAmount += statistics.get("5개 일치") * 1500000;
totalAmount += statistics.get("5개 일치, 보너스 볼 일치") * 30000000;
totalAmount += statistics.get("6개 일치") * 2000000000;
Comment on lines +61 to +73

Choose a reason for hiding this comment

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

따로 관리하는 도메인 클래스가 필요해 보여


return totalAmount;
}
}
84 changes: 84 additions & 0 deletions src/main/java/lotto/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package lotto.view;

import camp.nextstep.edu.missionutils.Console;
import lotto.dto.LottoDto;

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


public class InputView {
/* 구입 금액 입력 */
public static int getPurchaseAmount() {
while (true) {
try {
System.out.println("구입 금액을 입력해주세요.");
String input = Console.readLine();
validatePurchaseAmount(input);
return Integer.parseInt(input);
} catch (IllegalArgumentException e) {
System.out.println("[ERROR] " + e.getMessage());
}
}
}

private static void validatePurchaseAmount(String purchaseAmount) {
try {
int amount = Integer.parseInt(purchaseAmount);
if (amount <= 0) {
throw new IllegalArgumentException("구매 금액은 0보다 커야 합니다.");
}
if (amount % 1000 != 0) {
throw new IllegalArgumentException("구매 금액은 1000 단위로 나누어 떨어져야 합니다.");
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("구매 금액은 숫자여야 합니다.");
}
Comment on lines +26 to +36

Choose a reason for hiding this comment

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

validation 은 크게 두가지로 나뉘어 입력단계 검증과 도메인 단계 검증
입력 단계 검증인 경우 잘못 입력하거나 이런것들 예시로들면 숫자인데 문자를 입력했을경우고
도메인 단계 검증은 해당 로직에서의 조건이야 도메인 단계 검증은 도메인에서 해주면 되고 입력단계검증은 view는 출력만을 위한것이기 때문에 따로 클래스를 만들어 분리하여야 해

}

/* 당첨 금액 입력 */
public static LottoDto getWinningNumbers(){
while (true) {
try {
System.out.println("\n당첨 번호를 입력해주세요.");
String inputWinningNumbers = Console.readLine();
List<Integer> numbers = parseWinningNumbers(inputWinningNumbers);
return new LottoDto(numbers);
} catch (IllegalArgumentException e) {
System.out.println("[ERROR] " + e.getMessage());
}
}
}

private static List<Integer> parseWinningNumbers(String inputWinningNumbers) {
String[] numberStrings = inputWinningNumbers.split(",");
List<Integer> numbers = new ArrayList<>();

try {
for (String numberString : numberStrings) {
numbers.add(Integer.parseInt(numberString.trim()));
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("로또 번호는 숫자여야 합니다.");

Choose a reason for hiding this comment

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

에러 메시지를 정리해 놓는 클래스를 따로 두면 코드를 더 깔끔 하게 쓸 수 있고 재사용성이 올라가

}

return numbers;
}

/* 보너스 번호 입력 */
public static int getBonusNumber(){
while (true) {
try {
System.out.println("\n보너스 번호를 입력해주세요.");
String input = Console.readLine();
int bonusNumber = Integer.parseInt(input.trim());
if (bonusNumber < 1 || bonusNumber > 45) {
throw new IllegalArgumentException("보너스 번호는 1과 45 사이의 숫자여야 합니다.");
}
return Integer.parseInt(input.trim());
} catch (IllegalArgumentException e) {
System.out.println("[ERROR] " + e.getMessage());
}
}
Comment on lines +69 to +82

Choose a reason for hiding this comment

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

보너스 번호에 대한 예외 처리중 보너스 번호는 당첨 번호와 중복되지 않아야 한다가 누락되어 있어

}
}
34 changes: 34 additions & 0 deletions src/main/java/lotto/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package lotto.view;

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

public class OutputView {
/* 로또 구입 개수 출력 */
public static void printPurchaseCount(int numberOfLottos){
System.out.println("\n"+numberOfLottos+"개를 구매했습니다.");
}

/* 로또 번호 출력 */
public static void printLottoNumers(List<List<Integer>> lottoNumbers){
for (List<Integer> numbers : lottoNumbers) {
System.out.println(numbers);
}
}

/* 당첨 통계 출력 */
public static void printStatistics(Map<String, Integer> statistics) {
System.out.println("\n당첨 통계");
System.out.println("---");
System.out.println("3개 일치 (5,000원) - " + statistics.get("3개 일치") + "개");
System.out.println("4개 일치 (50,000원) - " + statistics.get("4개 일치") + "개");
System.out.println("5개 일치 (1,500,000원) - " + statistics.get("5개 일치") + "개");
System.out.println("5개 일치, 보너스 볼 일치 (30,000,000원) - " + statistics.get("5개 일치, 보너스 볼 일치") + "개");
System.out.println("6개 일치 (2,000,000,000원) - " + statistics.get("6개 일치") + "개");
}

/* 수익률 출력 */
public static void printProfitRate(double profitRate) {
System.out.printf("총 수익률은 %.1f%%입니다.\n", profitRate);
}
}
1 change: 1 addition & 0 deletions src/test/java/lotto/LottoTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package lotto;

import lotto.model.Lotto;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

Expand Down