-
Notifications
You must be signed in to change notification settings - Fork 357
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
4단계 - 로또(수동) #1128
base: pablo730
Are you sure you want to change the base?
4단계 - 로또(수동) #1128
Changes from 18 commits
3069eaa
d054953
fe63e9c
24366d5
aae3346
c5e0aad
6b7731b
7da206a
83f7e25
c5e6441
38ef9bf
69907e7
7c6c04f
47ff7e3
feb0e89
9da2ccd
b702361
13016fd
d56c98a
7ab86bb
a80a788
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,40 +1,59 @@ | ||
package lotto.controller | ||
|
||
import lotto.domain.LottoNumber | ||
import lotto.domain.LottoNumbers | ||
import lotto.domain.LottoTicketIssuer | ||
import lotto.domain.AutoLottoIssuer | ||
import lotto.domain.LottoNumberGenerator | ||
import lotto.domain.LottoPurchaseCalculator | ||
import lotto.domain.LottoTickets | ||
import lotto.domain.LottoWinnerNumbers | ||
import lotto.domain.PurchasedLottoTickets | ||
import lotto.domain.generateLottoNumbers | ||
import lotto.view.LottoPayoutView | ||
import lotto.view.PurchaseLottoResultView | ||
import lotto.view.ManualLottoView | ||
import lotto.view.PurchaseLottoView | ||
import lotto.view.WinnerLottoNumberView | ||
|
||
object LottoController { | ||
fun purchaseLotto(): PurchasedLottoTickets { | ||
fun getMaxPurchaseLottoCountFromPayment(): Int { | ||
val amountPaid = PurchaseLottoView.inputPurchaseCost() | ||
return LottoPurchaseCalculator.getMaxPurchasedLottoTicketCount(amountPaid) | ||
} | ||
|
||
fun purchaseManualLotto(maxPurchaseLottoCount: Int): LottoTickets { | ||
val manualLottoCount = ManualLottoView.inputManualLottoCount(maxPurchaseLottoCount) | ||
return ManualLottoView.repeatInputManualLottoNumbers(manualLottoCount) | ||
} | ||
|
||
val purchasedLottoTickets = | ||
LottoTicketIssuer.issueTickets(amountPaid = amountPaid, generateLottoNumbers = { generateLottoNumbers() }) | ||
fun createAutoLotto( | ||
maxPurchaseLottoCount: Int, | ||
manualLottoTickets: LottoTickets, | ||
): LottoTickets { | ||
val autoLottoCount = maxPurchaseLottoCount - manualLottoTickets.lottoTickets.size | ||
|
||
PurchaseLottoResultView.displayPurchaseLottoResults(purchasedLottoTickets = purchasedLottoTickets) | ||
val autoLottoTickets = | ||
AutoLottoIssuer.issueAutoLottoTickets(autoLottoCount) { | ||
LottoNumberGenerator.generateAutoLottoNumbers() | ||
} | ||
|
||
return purchasedLottoTickets | ||
PurchaseLottoView.displayPurchasedLottosView( | ||
manualLottoTickets = manualLottoTickets, | ||
autoLottoTickets = autoLottoTickets, | ||
) | ||
|
||
val combinedManualLottoAndAutoLotto = manualLottoTickets.lottoTickets.plus(autoLottoTickets.lottoTickets) | ||
|
||
return LottoTickets(combinedManualLottoAndAutoLotto) | ||
} | ||
|
||
fun createWinningLottoNumbers(): LottoWinnerNumbers { | ||
val inputLottoNumbers = WinnerLottoNumberView.inputWinningLottoNumbers() | ||
val lottoNumbers = LottoNumbers(inputLottoNumbers.map { LottoNumber.of(it) }.toSet()) | ||
val inputBonusNumber = WinnerLottoNumberView.inputBonusNumber() | ||
return LottoWinnerNumbers(lottoNumbers = lottoNumbers, bonusNumber = LottoNumber.of(inputBonusNumber)) | ||
|
||
return LottoWinnerNumbers(lottoNumbers = inputLottoNumbers, bonusNumber = inputBonusNumber) | ||
} | ||
|
||
fun resultPayout( | ||
purchasedLottoTickets: PurchasedLottoTickets, | ||
lottoTickets: LottoTickets, | ||
lottoWinnerNumbers: LottoWinnerNumbers, | ||
) { | ||
val purchasedLottoResults = lottoWinnerNumbers.resultLottoPayout(purchasedLottoTickets) | ||
val purchasedLottoResults = lottoWinnerNumbers.resultLottoPayout(lottoTickets) | ||
return LottoPayoutView.displayWinningStatistics(purchasedLottoResults = purchasedLottoResults) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package lotto.domain | ||
|
||
object AutoLottoIssuer { | ||
fun issueAutoLottoTickets( | ||
autoLottoCount: Int, | ||
generateLottoNumbers: () -> LottoNumbers, | ||
): LottoTickets { | ||
val autoLottoTickets = mutableListOf<LottoTicket.AutoLottoTicket>() | ||
|
||
repeat(autoLottoCount) { autoLottoTickets.add(LottoTicket.AutoLottoTicket { generateLottoNumbers() }) } | ||
|
||
return LottoTickets(autoLottoTickets) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,13 @@ | ||
package lotto.domain | ||
|
||
import lotto.domain.LottoNumber.Companion.LOTTO_NUMBER_MAX_VALUE | ||
import lotto.domain.LottoNumber.Companion.LOTTO_NUMBER_MIN_VALUE | ||
import lotto.domain.LottoNumbers.Companion.LOTTO_NUMBER_COUNT | ||
object LottoNumberGenerator { | ||
fun generateAutoLottoNumbers(): LottoNumbers { | ||
val randomNumbers = generatorRandomNumber() | ||
return LottoNumbers(randomNumbers.map { LottoNumber.of(it) }.toSet()) | ||
} | ||
|
||
fun generateLottoNumbers(): Set<Int> { | ||
return (LOTTO_NUMBER_MIN_VALUE..LOTTO_NUMBER_MAX_VALUE) | ||
.toSet().shuffled().take(LOTTO_NUMBER_COUNT).sorted().toSet() | ||
fun generatorRandomNumber(): List<Int> { | ||
return (LottoNumber.LOTTO_NUMBER_MIN_VALUE..LottoNumber.LOTTO_NUMBER_MAX_VALUE) | ||
.shuffled().take(LottoNumbers.LOTTO_NUMBER_COUNT).sorted() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package lotto.domain | ||
|
||
object LottoPurchaseCalculator { | ||
fun getMaxPurchasedLottoTicketCount(amountPaid: Int): Int { | ||
checkAmountPaid(amountPaid) | ||
return amountPaid / DEFAULT_LOTTO_PRICE | ||
} | ||
|
||
private fun checkAmountPaid(amountPaid: Int) { | ||
require(amountPaid >= DEFAULT_LOTTO_PRICE) { INVALID_MIN_COST_LOTTO_PAID_MESSAGE } | ||
require(amountPaid % DEFAULT_LOTTO_PRICE == CHECK_SURPLUS) { INVALID_THOUSAND_UNIT_LOTTO_PAID_MESSAGE } | ||
} | ||
|
||
const val DEFAULT_LOTTO_PRICE: Int = 1000 | ||
private const val CHECK_SURPLUS: Int = 0 | ||
private const val INVALID_MIN_COST_LOTTO_PAID_MESSAGE: String = "로또 구입 비용은 최소 1,000원 이상 이어야 합니다" | ||
private const val INVALID_THOUSAND_UNIT_LOTTO_PAID_MESSAGE: String = "로또 구입 비용은 1,000원 단위로 지불해야 합니다" | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,11 +1,17 @@ | ||||||
package lotto.domain | ||||||
|
||||||
data class LottoTicket(private val generateLottoNumbers: () -> Set<Int>) { | ||||||
val lottoNumbers = LottoNumbers(generateLottoNumbers().map { LottoNumber.of(it) }.toSet()) | ||||||
sealed class LottoTicket { | ||||||
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. sealed class 👍 sealed interface도 있는데 sealed class로 작성하신 이유와 이를 나누는 기준이 있을까요? (질문입니다!!) 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. sealed class인 LottoTicket은 상속을 통해 구체적인 구현체인 ManualLottoTicket과 AutoLottoTicket이 공통적으로 사용할 수 있는 메서드(checkLottoWinnerRank)를 제공하고 있는데요. 이처럼 구현체 간에 공통 기능이나 상태를 정의해야 할 때 sealed interface 보단 sealed class가 적합하다고 판단했습니다 만약, 클래스 내부에서 공유할 구체적인 구현이나 상태가 없다면, 특정 메서드를 구현하도록 강제하는 sealed interface를 고려했을 것 같습니다 sealed class와 sealed interface 둘 중에 무엇이 더 적절한지 다시 한번 생각해 볼 수 있는 좋은 질문을 주셔서 감사합니다! 혹시 다른 의견 있다면 피드백 부탁드립니다 🙇 |
||||||
abstract val lottoNumbers: LottoNumbers | ||||||
|
||||||
fun checkLottoWinnerRank(lottoWinnerNumbers: LottoWinnerNumbers): LottoWinnerRank { | ||||||
val matchCount = lottoNumbers.checkLottoNumbersMatch(lottoWinnerNumbers.lottoNumbers) | ||||||
val bonusCheck = lottoNumbers.contains(lottoWinnerNumbers.bonusNumber) | ||||||
return LottoWinnerRank.getRankByMatches(matchCount = matchCount, bonusCheck = bonusCheck) | ||||||
} | ||||||
|
||||||
class ManualLottoTicket(override val lottoNumbers: LottoNumbers) : LottoTicket() | ||||||
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. nested class로 선언하고 상속하신 이유가 있을까요?? 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. 당시 다른 로직에 신경쓰느라 이 부분은 깊게 고민하지 못하고 자연스럽게 nested class로 선언하게 된 것 같은데요 😢 다시 한번 nested class로 선언하게 된 주된 이유를 떠올려 봤을 때, 기능 구현상 LottoTicket과 관련된 클래스가 수동 로또, 자동 로또만 고려될 수 있고, LottoTicket의 checkLottoWinnerRank 메서드 외에 하위 클래스별로 서로 다른 메서드가 존재하지 않아 외부 클래스보다 nested class로 구현된 코드가 좀 더 이해하기 쉽고 관리하기가 용이할 것으로 판단한 것 같습니다 혹시 다른 의견이 있다면 말씀부탁드립니다! |
||||||
|
||||||
class AutoLottoTicket(private val generateLottoNumbers: () -> LottoNumbers) : LottoTicket() { | ||||||
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. generateLottoNumbers는 프로퍼티는 아닌걸로 보이네요! :)
Suggested change
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. 해당 함수는 AutoLottoTicket 생성 시점에만 호출되어 로또 번호를 생성하므로, 프로퍼티 대신 바로 생성자 내부에서 값을 초기화하는 방식으로 수정하는 것이 더 적절하겠네요! 덕분에 불필요한 프로퍼티 선언을 제거하고, 코드의 명확성과 간결성을 높일 수 있을 것 같습니다. 제안해주신대로 변경했습니다 👍 |
||||||
override val lottoNumbers: LottoNumbers = generateLottoNumbers() | ||||||
} | ||||||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package lotto.domain | ||
|
||
data class LottoTickets(val lottoTickets: List<LottoTicket>) |
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,40 @@ | ||||||||
package lotto.view | ||||||||
|
||||||||
import lotto.domain.LottoNumbers | ||||||||
import lotto.domain.LottoTicket.ManualLottoTicket | ||||||||
import lotto.domain.LottoTickets | ||||||||
import lotto.view.util.splitInputNumbersCommand | ||||||||
|
||||||||
object ManualLottoView { | ||||||||
fun inputManualLottoCount(maxPurchaseLottoCount: Int): Int { | ||||||||
println("\n수동으로 구매할 로또 수를 입력해 주세요.") | ||||||||
val inputManualLottoCountCommand: String? = readlnOrNull() | ||||||||
requireNotNull(inputManualLottoCountCommand) { "입력된 수동 로또 개수가 없습니다." } | ||||||||
|
||||||||
val inputManualLottoCount = inputManualLottoCountCommand.toIntOrNull() | ||||||||
requireNotNull(inputManualLottoCount) { "구입금액이 올바르게 입력되지 않았습니다." } | ||||||||
require(inputManualLottoCount >= 0) { "수동 로또 개수는 0개부터 입력 가능합니다." } | ||||||||
require(inputManualLottoCount <= maxPurchaseLottoCount) { "구입 가능한 수동 로또 개수를 초과했습니다." } | ||||||||
|
||||||||
return inputManualLottoCount | ||||||||
} | ||||||||
|
||||||||
fun repeatInputManualLottoNumbers(manualLottoCount: Int): LottoTickets { | ||||||||
println("\n수동으로 구매할 번호를 입력해 주세요.") | ||||||||
|
||||||||
val lottoNumbersList = mutableListOf<LottoNumbers>() | ||||||||
repeat(manualLottoCount) { lottoNumbersList.add(inputManualLottoNumbers()) } | ||||||||
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. 이런식으로도 작성이 가능해보이네요! :)
Suggested change
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. 제안해 주신 대로 코드를 작성하면 한 줄로 더 간결하게 표현할 수 있겠네요! map을 통해 ManualLottoTicket을 생성하는 부분이 바로 리스트 초기화와 결합되어 코드가 더 깔끔해진 것 같습니다. 가독성을 높일 수 있는 좋은 예시를 제안해 주셔서 감사합니다. |
||||||||
|
||||||||
val lottoTickets = lottoNumbersList.map { ManualLottoTicket(it) } | ||||||||
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. View에서 Tickets까지 만들어내는건 과한 책임인 것 같기도 한데요! List 혹은 List정도만 넘겨주면 어떨까요? 그러면서 중첩리스트를 반환하는게 괜찮은지? DTO를 만들어야하는지도 고민해보면 좋을 것 같아요! |
||||||||
|
||||||||
return LottoTickets(lottoTickets) | ||||||||
} | ||||||||
|
||||||||
private fun inputManualLottoNumbers(): LottoNumbers { | ||||||||
val inputManualNumbersCommand: String? = readlnOrNull() | ||||||||
|
||||||||
requireNotNull(inputManualNumbersCommand) { "입력된 수동 번호가 없습니다." } | ||||||||
|
||||||||
return splitInputNumbersCommand(inputManualNumbersCommand) | ||||||||
} | ||||||||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,26 @@ | ||
package lotto.view | ||
|
||
import lotto.domain.LottoTickets | ||
|
||
object PurchaseLottoView { | ||
fun inputPurchaseCost(): Int { | ||
println("구입금액을 입력해 주세요.") | ||
val inputCost: String? = readlnOrNull() | ||
requireNotNull(inputCost) { "구입금액이 입력되지 않았습니다" } | ||
requireNotNull(inputCost.toIntOrNull()) { "구입금액이 올바르게 입력되지 않았습니다" } | ||
|
||
return inputCost.toInt() | ||
} | ||
|
||
fun displayPurchasedLottosView( | ||
manualLottoTickets: LottoTickets, | ||
autoLottoTickets: LottoTickets, | ||
) { | ||
val manualLottoCount = manualLottoTickets.lottoTickets.size | ||
val autoLottoCount = autoLottoTickets.lottoTickets.size | ||
println("\n수동으로 ${manualLottoCount}장, 자동으로 ${autoLottoCount}개를 구매했습니다.") | ||
|
||
manualLottoTickets.lottoTickets.forEach { println(it.lottoNumbers.getNumbers()) } | ||
autoLottoTickets.lottoTickets.forEach { println(it.lottoNumbers.getNumbers()) } | ||
} | ||
} |
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.
마지막 return전까지 자동 LottoTickets객체와 수동 LottoTickets객체가 유의미하게 쓰인적이 없는것같아요!! (전부다 lottoTickets 프로퍼티를 꺼내서 검증을 진행했네요!)
자동, 수동 LottoTickets객체를 미리 만들 필요가 있는지 고민해보면 좋을 것 같습니다!