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

[로또 미션] 김명지 미션 제출합니다. #34

Open
wants to merge 27 commits into
base: starlight258
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5abe2a2
refactor: 리팩토링
Starlight258 Jun 18, 2024
685ce55
fix: 우승자 예외 처리 버그 수정
Starlight258 Jun 18, 2024
0f84cc2
refactor: 함수형 인터페이스 활용
Starlight258 Jun 18, 2024
7def5fb
refactor: 메서드 private으로 변경
Starlight258 Jun 18, 2024
6fbbe0b
test: WinningRank 테스트 작성
Starlight258 Jun 18, 2024
204eec4
refactor: 반환값 수정
Starlight258 Jun 18, 2024
dd3feb1
feat: 로또 수동 구매 기능 구현
Starlight258 Jun 22, 2024
3f1a41a
feat: LottoCount 값 객체 생성
Starlight258 Jun 22, 2024
e3c8191
refactor: inputView에서 outputView 의존성 제거
Starlight258 Jun 22, 2024
f01e1ac
fix: 가격 배수 더하기 버그 수정
Starlight258 Jun 22, 2024
2b05742
refactor: 리팩토링
Starlight258 Jun 22, 2024
48e7deb
refactor: 메서드 10줄 이하로 리팩토링
Starlight258 Jun 22, 2024
0232fab
test: LottoCount 테스트 추가
Starlight258 Jun 22, 2024
49c785c
test: 테스트 작성
Starlight258 Jun 22, 2024
c61bd49
refactor: 사용하지 않는 메서드 삭제
Starlight258 Jun 22, 2024
17e4d69
refactor: Lotto 내부 컬렉션 Set로 변경
Starlight258 Jun 22, 2024
dda74be
refactor: 매개변수에 final 사용하여 불변성 표현
Starlight258 Jun 22, 2024
6a3581d
rename: LottoResult를 LottoStatistics로 변경
Starlight258 Jun 23, 2024
63711c3
refactor: 필요없는 메서드 제거 및 메서드명 변경
Starlight258 Jun 23, 2024
c4f33e2
refactor: 입력을 받는 컬렉션에 방어적 복사 수행
Starlight258 Jun 23, 2024
20eb06c
docs: README 업데이트
Starlight258 Jun 23, 2024
7b9c6fa
refactor: 메서드 파라미터 타입 변경
Starlight258 Jul 1, 2024
e715e31
refactor: LottoStatistics에서 WinningRank 자체를 key로 사용
Starlight258 Jul 1, 2024
4e486aa
rename: LottoStatistics.java -> LottoResult.java
Starlight258 Jul 1, 2024
2ff823c
feat: lotto 출력 오름차순으로 수정
Starlight258 Jul 1, 2024
4188aec
rename: 패키지명 변경
Starlight258 Jul 1, 2024
2134c60
docs: README.md 업데이트
Starlight258 Jul 1, 2024
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
67 changes: 63 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
- `LottoNumber` : 로또 번호 VO
- `Lotto` : 로또 객체
- `Lottos` : 여러 개의 Lotto 객체를 관리하는 일급 컬렉션
- `LottoResult` : 로또 결과 VO
- `LottoStatistics` : 로또 당첨 통계 객체
- `LottoCount` : 로또 구매 개수 VO

#### constant

- `LottoBoundary` : 로또 경계값 상수
- `LottoMatch` : 로또 당첨 결과 상수
- `WinningRank` : 당첨 순위 Enum

## view

Expand Down Expand Up @@ -132,7 +132,8 @@
- 당첨 통계에 2등을 추가한다.
- 2등 당첨 조건은 당첨 번호 5개 일치 + 보너스 볼 일치다.

실행 결과

- 실행 결과

위 요구사항에 따라 14000원 어치 로또를 구매하였을 경우 프로그램을 실행한 결과는 다음과 같다.

Expand Down Expand Up @@ -174,3 +175,61 @@

### 새로운 프로그래밍 요구사항
Java Enum을 적용한다.

### 4단계 - 로또 수동 구매

- 현재 로또 생성기는 자동 생성 기능만 제공한다. 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다.
- 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다.


실행 결과

- 위 요구사항에 따라 14000원 어치 중 수동 3개, 자동 11개를 구매한 경우 프로그램을 실행한 결과는 다음과 같다.

```java
구입금액을 입력해 주세요.
14000

수동으로 구매할 로또 수를 입력해 주세요.
3

수동으로 구매할 번호를 입력해 주세요.
8, 21, 23, 41, 42, 43
3, 5, 11, 16, 32, 38
7, 11, 16, 35, 36, 44

수동으로 3장, 자동으로 11개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[23, 25, 33, 36, 39, 41]
[1, 3, 5, 14, 22, 45]
[5, 9, 38, 41, 43, 44]
[2, 8, 9, 18, 19, 21]
[13, 14, 18, 21, 23, 35]
[17, 21, 29, 37, 42, 45]
[3, 8, 27, 30, 35, 44]

지난 주 당첨 번호를 입력해 주세요.
1, 2, 3, 4, 5, 6

보너스 볼을 입력해 주세요.
7

당첨 통계
---------
3개 일치 (5000원)- 1개
4개 일치 (50000원)- 0개
5개 일치 (1500000원)- 0개
5개 일치, 보너스 볼 일치(30000000원) - 0개
6개 일치 (2000000000원)- 0개
총 수익률은 0.35입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)
```

### 5단계 - 리팩터링

- 기존 프로그래밍 요구사항을 다시 한번 확인하고, 학습 테스트를 통해 학습한 내용을 반영한다.
2 changes: 1 addition & 1 deletion src/main/java/org/duckstudy/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class Application {
public static void main(String[] args) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
OutputView outputView = new OutputView();
InputView inputView = new InputView(bufferedReader, outputView);
InputView inputView = new InputView(bufferedReader);

LottoController lottoController = new LottoController(outputView, inputView);
lottoController.run();
Expand Down
89 changes: 67 additions & 22 deletions src/main/java/org/duckstudy/controller/LottoController.java
Copy link
Member

Choose a reason for hiding this comment

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

(반영하지 않으셔도 됩니다.) 엄밀히 따지면 Controller가 아니며, MVC에서의 Conroller와도 달라요.

일단 MVC에서의 Controller를 먼저 이야기하면 Controller는 View를 들고 실제로 랜더링하는 역할은 아니에요. 단순한 콘솔게임이기 때문에 MVC 패턴의 복잡한 정의를 사용하여 구현할 필요는 없어요.

그래서 MVC 패턴을 제거하고보면 Controller라는 네이밍은 클래스와는 어울리지 않아요. Controller는 영문법의 문법상 게임을 컨트롤하는 조이스틱과 같은 의미에요. 조이스틱이 화면을 가지고 게임을 실행하고 출력하는건 맞지 않는거죠. 조이스틱이 컴퓨터나 엔진이 아니니까요.

디자인 패턴은 정의된 것을 그대로 상황에 끼워 사용하는 것이 아닌 특정한 상황에 해결책으로 나온 것이기 때문에 개인적으로 패턴종속적인 학습이나 개발을 하고 계시다면 개념 자체에 매몰될 수 있어요. 향후에도 개발하실때는 네이밍을 너무 패턴 종속적으로 보고 잡고있지는 않는가를 계속 고민하면서 개발하면 도움이 될 것 같아요.

Copy link
Author

@Starlight258 Starlight258 Jul 1, 2024

Choose a reason for hiding this comment

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

그렇네요..! Controller라고 하기엔 잘못된 입력을 반복적으로 받는 로직도 있고, 비즈니스 로직도 호출하고 여러가지를 하고 있네요.
MVC 패턴으로 먼저 구조를 잡고 개발하다보니 콘솔 애플리케이션 구현에는 적절하지 않은 설계가 된 것 같아요.
앞으로도 너무 틀에 갇혀서 개발하지 않고 시야를 넓히는 식으로 공부하겠습니다. 감사합니다 !! 😃

Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package org.duckstudy.controller;

import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.duckstudy.model.Price;
import org.duckstudy.model.lotto.Lotto;
import org.duckstudy.model.lotto.LottoCount;
import org.duckstudy.model.lotto.LottoNumber;
import org.duckstudy.model.lotto.LottoResult;
import org.duckstudy.model.lotto.LottoStatistics;
import org.duckstudy.model.lotto.Lottos;
import org.duckstudy.view.InputView;
import org.duckstudy.view.OutputView;
Expand All @@ -13,23 +16,36 @@ public class LottoController {
private final OutputView outputView;
private final InputView inputView;

public LottoController(OutputView outputView, InputView inputView) {
public LottoController(final OutputView outputView, final InputView inputView) {
this.outputView = outputView;
this.inputView = inputView;
}

public void run() {
Price price = createPrice();
Lottos lottos = Lottos.generateLottosByPrice(price);
outputView.printLottos(lottos);
Lottos totalLottos = createTotalLottos(price);

Lotto winningLotto = createWinningLotto();
LottoNumber bonusNumber = createBonusNumber(winningLotto);

getWinningResult(price, lottos, winningLotto, bonusNumber);
printWinningResult(price, totalLottos, winningLotto, bonusNumber);
}

private Lottos createTotalLottos(final Price price) {
LottoCount totalLottoCount = price.calculateLottoCount();
LottoCount manualLottoCount = createManualLottoCount(totalLottoCount);
Lottos manualLottos = createManualLottos(manualLottoCount.getCount());

LottoCount autoLottoCount = totalLottoCount.subtract(manualLottoCount);
Lottos autoLottos = Lottos.generateLottos(autoLottoCount.getCount());
Lottos totalLottos = manualLottos.merge(autoLottos);

outputView.printLottos(manualLottoCount.getCount(), autoLottoCount.getCount(), totalLottos);
return totalLottos;
}

private Price createPrice() {
outputView.printInputPrice();
try {
Price price = new Price(inputView.inputPrice());
price.validateInputPrice();
Expand All @@ -40,7 +56,37 @@ private Price createPrice() {
}
}

private Lottos createManualLottos(final int manualLottoCount) {
outputView.printInputManualLotto();

return new Lottos(IntStream.range(0, manualLottoCount)
.mapToObj(i -> createManualLotto())
.collect(Collectors.toList()));
}

private LottoCount createManualLottoCount(final LottoCount lottoCount) {
outputView.printInputManualLottoCount();
try {
LottoCount manualLottoCount = new LottoCount(inputView.inputManualLottoCount());
manualLottoCount.validateManualLottoCount(lottoCount.getCount());
return manualLottoCount;
} catch (IllegalArgumentException e) {
outputView.printException(e);
return createManualLottoCount(lottoCount);
}
}

private Lotto createManualLotto() {
try {
return Lotto.from(inputView.inputManualLotto());
} catch (IllegalArgumentException e) {
outputView.printException(e);
return createManualLotto();
}
}

private Lotto createWinningLotto() {
outputView.printInputWinningLotto();
try {
return Lotto.from(inputView.inputWinningLotto());
} catch (IllegalArgumentException e) {
Expand All @@ -49,30 +95,29 @@ private Lotto createWinningLotto() {
}
}

private LottoNumber createBonusNumber(Lotto winningLotto) {
LottoNumber bonusNumber = LottoNumber.valueOf(inputView.inputBonusNumber());
if (winningLotto.containsNumber(bonusNumber)) {
outputView.printExceptionForBonusNumber();
private LottoNumber createBonusNumber(final Lotto winningLotto) {
outputView.printInputBonusNumber();
try {
LottoNumber bonusNumber = LottoNumber.valueOf(inputView.inputBonusNumber());
validateBonusNumber(winningLotto, bonusNumber);
return bonusNumber;
} catch (IllegalArgumentException e) {
outputView.printException(e);
return createBonusNumber(winningLotto);
}
outputView.printExceptionForBonusNumber();
return bonusNumber;
}

private void getWinningResult(Price price, Lottos lottos, Lotto winningLotto, LottoNumber bonusNumber) {
LottoResult result = createLottoResult(lottos, winningLotto, bonusNumber);

calculateProfitRate(price, result);
private void validateBonusNumber(final Lotto winningLotto, final LottoNumber bonusNumber) {
if (winningLotto.containsNumber(bonusNumber)) {
throw new IllegalArgumentException("당첨 번호와 중복되는 보너스 볼은 입력할 수 없습니다.");
}
}

private LottoResult createLottoResult(Lottos lottos, Lotto winningLotto, LottoNumber bonusNumber) {
LottoResult result = lottos.accumulateLottoResult(winningLotto, bonusNumber);
outputView.printLottoResult(result);
return result;
}
private void printWinningResult(Price price, Lottos totalLottos, Lotto winningLotto, LottoNumber bonusNumber) {
LottoStatistics statistics = totalLottos.accumulateLottoResult(winningLotto, bonusNumber);
outputView.printLottoStatistics(statistics);

private void calculateProfitRate(Price price, LottoResult result) {
double profitRate = price.calculateProfitRate(result);
double profitRate = price.calculateProfitRate(statistics);
outputView.printTotalProfit(profitRate);
}
}
32 changes: 14 additions & 18 deletions src/main/java/org/duckstudy/model/Price.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package org.duckstudy.model;

import java.util.Objects;
import org.duckstudy.model.lotto.LottoResult;
import org.duckstudy.model.lotto.LottoCount;
import org.duckstudy.model.lotto.LottoStatistics;
import org.duckstudy.model.lotto.constant.WinningRank;

public class Price {
Expand All @@ -13,16 +14,16 @@ public class Price {

private final int value;

public Price(int price) {
public Price(final int price) {
Copy link
Member

Choose a reason for hiding this comment

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

파라미터들에 final이 달리게된 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

입력값이 객체라면 참조가 변경되는 것을 막고, 기본 값이라면 값 자체가 변경되는 것을 막아서 실수를 방지할 수 있기 때문입니다.

validatePrice(price);
this.value = price;
}

public static Price initialize() {
public static Price zero() {
return new Price(INCLUSIVE_MIN_PRICE);
}

private void validatePrice(int price) {
private void validatePrice(final int price) {
if (price < INCLUSIVE_MIN_PRICE) {
throw new IllegalArgumentException(String.format("가격은 %d원 이상이어야 합니다.", INCLUSIVE_MIN_PRICE));
}
Expand All @@ -34,41 +35,36 @@ public void validateInputPrice() {
}
}

public Price addPrice(int value) {
public Price addPrice(final int value) {
return new Price(this.value + value);
}

public Price multiplyTimes(int times) {
return new Price(value * times);
}

public double divideByPrice(Price divisor) {
public double divideByPrice(final Price divisor) {
checkIfZero(divisor.getValue());
return (double) value / divisor.getValue();
}

private void checkIfZero(int divisor) {
private void checkIfZero(final int divisor) {
if (divisor == ZERO) {
throw new IllegalArgumentException(String.format("%d으로 나눌 수 없습니다.", ZERO));
}
}

public int calculateLottoCount() {
public LottoCount calculateLottoCount() {
checkIfZero(LOTTO_PRICE);
return value / LOTTO_PRICE;
return new LottoCount(value / LOTTO_PRICE);
}

public double calculateProfitRate(LottoResult result) {
Price profit = Price.initialize();
public double calculateProfitRate(final LottoStatistics result) {
Price profit = Price.zero();
for (WinningRank winningRank : WinningRank.values()) {
profit = profit.accumulateProfit(winningRank, result.getMatchingCount(winningRank.getKey()));
}
return profit.divideByPrice(this) * PERCENT_BASE;
}

private Price accumulateProfit(WinningRank winningRank, int count) {
return this.addPrice(winningRank.getPrice())
.multiplyTimes(count);
private Price accumulateProfit(final WinningRank winningRank, final int count) {
return this.addPrice(winningRank.getPrice() * count);
}

public int getValue() {
Expand Down
Loading