-
Notifications
You must be signed in to change notification settings - Fork 76
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
[로또 2단계] 시아 미션 제출합니다. #135
base: leeyerin0210
Are you sure you want to change the base?
Changes from 24 commits
79bc0e4
133dce9
02cb6d7
29794c2
d9ec1a0
66b2a02
1907483
6a9e9d9
4ed8d62
1db3c57
4485eac
6dd8b3d
63bb963
e401cbd
d07db8c
422eaac
0050e23
e7f5664
8eb1895
8444009
0fd886a
fece2a8
0220a90
09eb61b
19253ff
33d22ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,12 @@ | ||
package lotto | ||
|
||
import lotto.controller.LottoController | ||
import lotto.view.InputView | ||
import lotto.view.OutputView | ||
|
||
fun main() { | ||
val lottoController = LottoController() | ||
lottoController.run() | ||
val inputView = InputView() | ||
val outputView = OutputView() | ||
val controller = LottoController(inputView, outputView) | ||
controller.run() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,106 @@ | ||
package lotto.controller | ||
|
||
import lotto.model.Amount | ||
import lotto.model.Lotto | ||
import lotto.model.LottoMachine | ||
import lotto.model.LottoMatcher | ||
import lotto.model.LottoNumber | ||
import lotto.model.LottoNumbers | ||
import lotto.model.PrizeCalculator | ||
import lotto.domain.model.Amount | ||
import lotto.domain.model.Lotto | ||
import lotto.domain.model.LottoCreationResult | ||
import lotto.domain.model.LottoNumber | ||
import lotto.domain.model.Rank | ||
import lotto.domain.model.WinningLotto | ||
import lotto.domain.model.WinningLottoCreationResult | ||
import lotto.domain.service.RankCalculator | ||
import lotto.domain.service.WinningListMaker | ||
import lotto.view.InputView | ||
import lotto.view.Message | ||
import lotto.view.OutputView | ||
|
||
class LottoController( | ||
private val inputView: InputView = InputView(), | ||
private val outputView: OutputView = OutputView(), | ||
val inputView: InputView, | ||
val outputView: OutputView, | ||
) { | ||
val amount = inputAmount() | ||
|
||
fun run() { | ||
val amount = getAmount() | ||
val lottoMachine = LottoMachine(amount) | ||
val publishedLotto = publishLotto(lottoMachine) | ||
val count = getManualCount() | ||
val manualLottoList = getLottoList(count) | ||
val autoLottoList = getAutoLotto(amount.getCount(LOTTO_PRIZE) - count) | ||
|
||
outputView.printPurchaseResult(manualLottoList, autoLottoList) | ||
|
||
val winningLotto = getWinningLotto() | ||
val bonusNumber = getBonusNumber() | ||
val lottoMatcher = LottoMatcher(winningLotto, bonusNumber) | ||
showEarningRate(amount, lottoMatcher, publishedLotto) | ||
val ranks = WinningListMaker().calculateRanks(winningLotto, manualLottoList + autoLottoList) | ||
|
||
val sortedResults = sortResultsByOriginalRankOrder(ranks) | ||
val totalWinnings = RankCalculator().earningMoney(ranks) | ||
val earningRate = RankCalculator().calculateEarningRate(amount.money, totalWinnings) | ||
|
||
outputView.printResult(sortedResults, earningRate) | ||
} | ||
|
||
private fun getAmount(): Amount = Amount(inputView.getMoney()) | ||
fun getAutoLotto(count: Int): List<Lotto> = List(count) { Lotto.createRandom() } | ||
|
||
private fun publishLotto(lottoMachine: LottoMachine): List<Lotto> { | ||
val publishedLotto = lottoMachine.publishLottoTickets(Amount(LOTTO_PRIZE)) | ||
outputView.printPublishedLotto(publishedLotto) | ||
return publishedLotto | ||
fun getManualCount(): Int { | ||
val manualCount = inputView.getManualCount() | ||
return if (amount.getCount(LOTTO_PRIZE) >= manualCount) { | ||
manualCount | ||
} else { | ||
outputView.printErrorMessage(Message.errorCountExceeded()) | ||
getManualCount() | ||
} | ||
} | ||
|
||
private fun getWinningLotto(): Lotto { | ||
val winningInput = inputView.getWinningLotto() | ||
return Lotto(LottoNumbers(winningInput.map { number -> LottoNumber(number) }), Amount(LOTTO_PRIZE)) | ||
fun getLottoList(count: Int): List<Lotto> { | ||
inputView.messageManualLotto() | ||
return List(count) { inputLotto() } | ||
} | ||
|
||
private fun getBonusNumber(): LottoNumber = LottoNumber(inputView.getBonusNumber()) | ||
|
||
private fun showEarningRate( | ||
amount: Amount, | ||
lottoMatcher: LottoMatcher, | ||
publishedLotto: List<Lotto>, | ||
) { | ||
val result = lottoMatcher.matchLotto(publishedLotto) | ||
val prizeCalculator = PrizeCalculator() | ||
val earningRate = prizeCalculator.calculateEarningRate(amount.money, result) | ||
outputView.printPrize(result, earningRate) | ||
private fun inputLotto(): Lotto { | ||
when ( | ||
val result = | ||
Lotto.create( | ||
inputView | ||
.getManualLotto() | ||
.mapNotNull { LottoNumber.createOrNull(it) } | ||
.sortedBy { it.value }, | ||
) | ||
) { | ||
is LottoCreationResult.Success -> return result.lotto | ||
is LottoCreationResult.Failure.InvalidCount -> outputView.printErrorMessage(Message.errorInvalidLotto()) | ||
is LottoCreationResult.Failure.DuplicatedNumbers -> outputView.printErrorMessage(Message.errorInvalidLotto()) | ||
is LottoCreationResult.Failure.NotSorted -> outputView.printErrorMessage(Message.errorInvalidLotto()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 커스텀 예외를 만들었지만 결국 동일한 출력이 발생하는 거라면 오버엔지니어링으로 보이네요! runCatching {
val lotto = Lotto.valueOf(...)
}.getOrElse { ... } |
||
} | ||
return inputLotto() | ||
} | ||
|
||
private fun inputAmount(): Amount { | ||
val amount = Amount.createOrNull(inputView.getMoney()) | ||
return amount ?: run { | ||
outputView.printErrorMessage(Message.errorInvalidAmount()) | ||
inputAmount() | ||
} | ||
} | ||
|
||
private fun getWinningLotto(): WinningLotto { | ||
when ( | ||
val result = | ||
WinningLotto.create( | ||
inputView.getWinningLotto().mapNotNull { LottoNumber.createOrNull(it) }, | ||
LottoNumber.createOrNull(inputView.getBonusNumber()) ?: return getWinningLotto(), | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 코드 깊이가 너무 들어갔어요. |
||
) { | ||
is WinningLottoCreationResult.Success -> return result.winningLotto | ||
is WinningLottoCreationResult.Failure.NumberSizeError -> outputView.printErrorMessage(Message.errorInvalidWinningNumbers()) | ||
is WinningLottoCreationResult.Failure.BonusNumberDuplicated -> outputView.printErrorMessage(Message.errorInvalidBonusNumber()) | ||
is WinningLottoCreationResult.Failure.DuplicatedNumbers -> outputView.printErrorMessage(Message.errorInvalidWinningNumbers()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
왜 괜찮다고 생각하는지 궁금합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. result 타입을 그대로 뷰에 전달하면 뷰가 너무 도메인에 대한 세부 정보를 알게 되는 것이 아닌가..해서 이렇게 작성했습니다! |
||
} | ||
return getWinningLotto() | ||
} | ||
|
||
private fun sortResultsByOriginalRankOrder(ranks: List<Rank>): List<Pair<Rank, Int>> = | ||
Rank.entries | ||
.filter { it != Rank.MISS } | ||
.map { rank -> rank to ranks.count { it == rank } } | ||
.reversed() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 당첨 로또가 몇개인지 계산하는 비즈니스 로직이 왜 컨트롤러에 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 33d22ea |
||
|
||
companion object { | ||
const val LOTTO_PRIZE = 1000 | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package lotto.domain.model | ||
|
||
class Amount private constructor( | ||
val money: Int, | ||
) { | ||
fun getCount(lottoPrize: Int): Int = money / lottoPrize | ||
|
||
fun paymentOrNull(payMoney: Int): Amount? { | ||
if (money < payMoney) return null | ||
return Amount(money - payMoney) | ||
} | ||
|
||
companion object { | ||
fun createOrNull(input: Int): Amount? = if (input >= 0) Amount(input) else null | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package lotto.domain.model | ||
|
||
interface SortStrategy { | ||
fun sort(numberList: List<LottoNumber>): List<LottoNumber> | ||
} | ||
|
||
class RandomSort : SortStrategy { | ||
override fun sort(numberList: List<LottoNumber>): List<LottoNumber> = numberList.shuffled() | ||
} | ||
|
||
sealed class LottoCreationResult { | ||
data class Success( | ||
val lotto: Lotto, | ||
) : LottoCreationResult() | ||
|
||
sealed class Failure : LottoCreationResult() { | ||
object NotSorted : Failure() | ||
|
||
object InvalidCount : Failure() | ||
|
||
object DuplicatedNumbers : Failure() | ||
} | ||
} | ||
|
||
class Lotto private constructor( | ||
val numberList: List<LottoNumber>, | ||
) { | ||
init { | ||
require(numberList.sortedBy { it.value } == numberList) { throw IllegalArgumentException() } | ||
} | ||
|
||
companion object { | ||
const val LOTTO_NUMBER_QUANTITY = 6 | ||
private val LOTTO_NUMBERS: List<LottoNumber> = (1..45).map { LottoNumber.valueOf(it) } | ||
|
||
fun create(numberList: List<LottoNumber>): LottoCreationResult = | ||
when { | ||
numberList.size != LOTTO_NUMBER_QUANTITY -> LottoCreationResult.Failure.InvalidCount | ||
numberList.distinctBy { it.value }.size != numberList.size -> LottoCreationResult.Failure.DuplicatedNumbers | ||
else -> LottoCreationResult.Success(Lotto(numberList.sortedBy { it.value })) | ||
} | ||
|
||
fun createRandom(sortStrategy: SortStrategy = RandomSort()): Lotto = | ||
Lotto(sortStrategy.sort(LOTTO_NUMBERS).take(LOTTO_NUMBER_QUANTITY).sortedBy { it.value }) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package lotto.domain.model | ||
|
||
data class LottoNumber private constructor( | ||
val value: Int, | ||
) { | ||
companion object { | ||
private val VALID_RANGE = 1..45 | ||
private const val RANGE_ERROR = "[ERROR] 범위 외의 값입니다." | ||
|
||
fun valueOf(value: Int): LottoNumber { | ||
require(value in VALID_RANGE) { RANGE_ERROR } | ||
return LottoNumber(value) | ||
} | ||
|
||
fun createOrNull(value: Int): LottoNumber? { | ||
if (validation(value)) return LottoNumber(value) | ||
return null | ||
} | ||
|
||
private fun validation(value: Int): Boolean = value in VALID_RANGE | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package lotto.domain.model | ||
|
||
enum class Rank( | ||
val countOfMatch: Int, | ||
val winningMoney: Int, | ||
val matchBonus: Boolean, | ||
) { | ||
FIRST(6, 2_000_000_000, false), | ||
SECOND(5, 30_000_000, true), | ||
THIRD(5, 1_500_000, false), | ||
FOURTH(4, 50_000, false), | ||
FIFTH(3, 5_000, false), | ||
MISS(0, 0, false), | ||
; | ||
|
||
companion object { | ||
fun valueOf( | ||
countOfMatch: Int, | ||
matchBonus: Boolean, | ||
): Rank = | ||
entries.firstOrNull { | ||
(it.countOfMatch <= countOfMatch) && ((it.matchBonus && matchBonus) == it.matchBonus) | ||
} ?: MISS | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package lotto.domain.model | ||
|
||
sealed class WinningLottoCreationResult { | ||
data class Success( | ||
val winningLotto: WinningLotto, | ||
) : WinningLottoCreationResult() | ||
|
||
sealed class Failure : WinningLottoCreationResult() { | ||
object BonusNumberDuplicated : Failure() | ||
|
||
object DuplicatedNumbers : Failure() | ||
|
||
object NumberSizeError : Failure() | ||
} | ||
} | ||
|
||
class WinningLotto private constructor( | ||
private val lottoNumbers: List<LottoNumber>, | ||
private val bonusNumber: LottoNumber, | ||
) { | ||
fun findRank(lotto: Lotto): Rank { | ||
val countOfMatch = lottoNumbers.intersect(lotto.numberList).size | ||
val bonusMatched = lotto.numberList.contains(bonusNumber) | ||
return Rank.valueOf(countOfMatch, bonusMatched) | ||
} | ||
|
||
companion object { | ||
const val WINNING_LOTTO_NUMBER_QUANTITY = 6 | ||
|
||
fun create( | ||
numbers: List<LottoNumber>, | ||
bonusNumber: LottoNumber, | ||
): WinningLottoCreationResult = | ||
when { | ||
numbers.size != WINNING_LOTTO_NUMBER_QUANTITY -> WinningLottoCreationResult.Failure.NumberSizeError | ||
numbers.distinctBy { it.value }.size != numbers.size -> WinningLottoCreationResult.Failure.DuplicatedNumbers | ||
numbers.contains(bonusNumber) -> WinningLottoCreationResult.Failure.BonusNumberDuplicated | ||
else -> WinningLottoCreationResult.Success(WinningLotto(numbers, bonusNumber)) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package lotto.domain.service | ||
|
||
import lotto.domain.model.Rank | ||
|
||
class RankCalculator { | ||
fun earningMoney(winningList: List<Rank>): Int = winningList.sumOf { it.winningMoney } | ||
|
||
fun calculateEarningRate( | ||
inputMoney: Int, | ||
earningMoney: Int, | ||
): Double { | ||
require(inputMoney != 0) { "[ERROR] 입력 금액이 0입니다" } | ||
return earningMoney.toDouble() / inputMoney.toDouble() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package lotto.domain.service | ||
|
||
import lotto.domain.model.Lotto | ||
import lotto.domain.model.Rank | ||
import lotto.domain.model.WinningLotto | ||
|
||
class WinningListMaker { | ||
fun calculateRanks( | ||
winningLotto: WinningLotto, | ||
lottoList: List<Lotto>, | ||
): List<Rank> = lottoList.map { winningLotto.findRank(it) } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
컨트롤러의 함수 접근자를 public으로 열 필요가있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
19253ff
함수 접근자 확인하고 private으로 수정했습니다! 기본적으로 public으로 작성하는 안 좋은 습관이 있었던 것 같습니다.