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

Step4 - 로또(수동) #997

Open
wants to merge 7 commits into
base: sodp5
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
38 changes: 28 additions & 10 deletions src/main/kotlin/lotto/Main.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
package lotto

import lotto.domain.DefaultLottoGenerateStrategy
import lotto.domain.AutoLottoNumbersGenerateStrategy
import lotto.domain.Lotto
import lotto.domain.LottoGenerator
import lotto.domain.LottoNumber
import lotto.domain.LottoStore
import lotto.domain.Lottos
import lotto.domain.ManualLottoNumbersGenerateStrategy
import lotto.domain.WinningLotto
import lotto.ui.InputView
import lotto.ui.ResultView

fun main() {
val generator = LottoGenerator(DefaultLottoGenerateStrategy())
val store = LottoStore(generator)
val store = LottoStore()

val money = InputView.inputMoney()
val boughtLottos = store.buyLottos(money)
ResultView.showBoughtLottos(boughtLottos)
val boughtManualLottoCounts = InputView.inputManualLottoCount()

val winningLotto = WinningLotto(Lotto(InputView.inputWinningNumbers()), InputView.inputBonusNumber())
val manualLottosPrice = boughtManualLottoCounts * LottoStore.LOTTO_PRICE

val checkedLottos = boughtLottos.matchAll(winningLotto)
val returnRate = checkedLottos.totalReward() / money.toDouble()
require(money >= manualLottosPrice) {
"로또를 구매할 돈이 부족합니다."
Comment on lines +19 to +22
Copy link

Choose a reason for hiding this comment

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

돈에 관한 로직들이 생겨나고 있는 것 같은데요 돈에 관련된 값 객체 등을 만들어서 도메인에 로직을 두면 좋을 것 같아요.
지금은 컨트롤러에 로직들이 있어서 테스트가 어려워지는 것 같습니다.

}

ResultView.showResult(checkedLottos, returnRate)
val manualLottos = List(boughtManualLottoCounts) {
val lottoNumbers = InputView.inputManualLottoNumber()

store.buyLotto(ManualLottoNumbersGenerateStrategy(lottoNumbers))
}.let { Lottos(it) }

val autoLottos = store.buyLottos(money - manualLottosPrice, AutoLottoNumbersGenerateStrategy())

val boughtLottos = manualLottos + autoLottos

ResultView.showBoughtLottos(boughtLottos, boughtManualLottoCounts)

val winningLotto = WinningLotto(Lotto.of(InputView.inputWinningNumbers()), LottoNumber(InputView.inputBonusNumber()))
val matchedLotto = boughtLottos.matchAll(winningLotto)

val returnRate = matchedLotto.totalReward() / money.toDouble()

ResultView.showResult(matchedLotto, returnRate)
}
12 changes: 12 additions & 0 deletions src/main/kotlin/lotto/domain/AutoLottoNumbersGenerateStrategy.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package lotto.domain

class AutoLottoNumbersGenerateStrategy : LottoNumbersGenerateStrategy {
private val numberPool: IntRange = LottoNumber.MIN_NUMBER..LottoNumber.MAX_NUMBER
override fun generate(): LottoNumbers {
return numberPool
.shuffled()
.subList(0, Lotto.NUMBERS_COUNT)
.sorted()
.let { LottoNumbers.of(it) }
}
}
12 changes: 0 additions & 12 deletions src/main/kotlin/lotto/domain/DefaultLottoGenerateStrategy.kt

This file was deleted.

39 changes: 10 additions & 29 deletions src/main/kotlin/lotto/domain/Lotto.kt
Original file line number Diff line number Diff line change
@@ -1,41 +1,22 @@
package lotto.domain

class Lotto(
val numbers: List<Int>,
val winning: LottoWinning = LottoWinning.Miss,
val numbers: LottoNumbers,
) {
init {
require(numbers.size == NUMBERS_COUNT) {
"로또 번호는 항상 ${NUMBERS_COUNT}개 여야 합니다."
}
require(numbers.distinct().size == NUMBERS_COUNT) {
"로또 번호는 중복이 없어야 합니다."
}
require(numbers.all { it in MIN_NUMBER..MAX_NUMBER }) {
"로또 번호는 항상 ${MIN_NUMBER}에서 ${MAX_NUMBER}사이 값이어야 합니다."
}
require(numbers.isSortedNumber()) {
"로또 번호는 항상 정렬되어야 합니다."
}
}

fun match(winningLotto: WinningLotto): Lotto {
val correctCount = winningLotto.lotto.numbers.count { numbers.contains(it) }
fun match(winningLotto: WinningLotto): MatchedLotto {
val correctCount = winningLotto.lotto.numbers.containsCount(numbers)
val matchBonus = numbers.contains(winningLotto.bonusNumber)
Comment on lines +7 to 8
Copy link

Choose a reason for hiding this comment

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

getter를 없애는 방법은 디미터의 법칙에 대해서 읽어보시면 좋을 것 같네요.
간단하게는 값을 꺼내서 로직을 구현하는 대신 책임이 있는 객체에게 메세지를 보내는 방식이라고 생각하시면 될 것 같아요.


return Lotto(
numbers = numbers,
winning = LottoWinning.of(correctCount, matchBonus)
)
}

private fun List<Int>.isSortedNumber(): Boolean {
return zipWithNext { a, b -> a <= b }.all { it }
return MatchedLotto(this, LottoWinning.of(correctCount, matchBonus))
}

companion object {
const val NUMBERS_COUNT = 6
const val MIN_NUMBER = 1
const val MAX_NUMBER = 45

fun of(numbers: List<Int>): Lotto {
val lottoNumbers = LottoNumbers(numbers.map { LottoNumber(it) })

return Lotto(lottoNumbers)
}
}
}
5 changes: 0 additions & 5 deletions src/main/kotlin/lotto/domain/LottoGenerateStrategy.kt

This file was deleted.

6 changes: 3 additions & 3 deletions src/main/kotlin/lotto/domain/LottoGenerator.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package lotto.domain

class LottoGenerator(private val lottoGenerateStrategy: LottoGenerateStrategy) {
fun publish(): Lotto {
return lottoGenerateStrategy.generate()
class LottoGenerator {
fun publish(lottoNumbersGenerateStrategy: LottoNumbersGenerateStrategy): Lotto {
return Lotto(lottoNumbersGenerateStrategy.generate())
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/lotto/domain/LottoNumber.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package lotto.domain

@JvmInline
value class LottoNumber(val value: Int) : Comparable<LottoNumber> {
init {
require(value in MIN_NUMBER..MAX_NUMBER) {
"로또 번호는 항상 ${MIN_NUMBER}에서 ${MAX_NUMBER}사이 값이어야 합니다."
}
}

override fun compareTo(other: LottoNumber): Int {
return value.compareTo(other.value)
}

companion object {
const val MIN_NUMBER = 1
const val MAX_NUMBER = 45
}
}
49 changes: 49 additions & 0 deletions src/main/kotlin/lotto/domain/LottoNumbers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package lotto.domain

@JvmInline
value class LottoNumbers(private val value: List<LottoNumber>) {

init {
require(value.size == Lotto.NUMBERS_COUNT) {
"로또 번호는 항상 ${Lotto.NUMBERS_COUNT}개 여야 합니다."
Comment on lines +7 to +8
Copy link

Choose a reason for hiding this comment

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

count 상수도 여기 위치하는게 맞지 않을까요?

}
require(!hasDuplicate()) {
"로또 번호는 중복이 없어야 합니다."
}
require(isSorted()) {
"로또 번호는 항상 정렬되어야 합니다."
}
}

val size: Int
get() = value.size

val numbers: List<Int>
get() = value.map { it.value }
Comment on lines +21 to +22
Copy link

Choose a reason for hiding this comment

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

저는 값 객체로 감싼 값들은 어플리케이션의 종단에서만 꺼내서 사용하려고 하는 편입니다. 예를 들면 View에 값을 넣어주기 직전이나 DB 등에 값을 넣기 직전이 되겠죠.

원시값을 객체로 감쌀 때는 그 이유가 있을 거에요. 타입 구분을 위함이라던지 값에 로직을 넣는다던지요. 그런데 어플리케이션 중간에 그 값을 꺼내서 사용해버리면 이렇게 감싼 이유가 사라지게됩니다. LottoNumbers의 클라이언트 입장에서는 이 값이 로또 번호라는 보장을 잃게 되겠죠.


fun contains(lottoNumber: LottoNumber): Boolean {
return value.contains(lottoNumber)
}

fun containsCount(other: LottoNumbers): Int {
return value.count { other.value.contains(it) }
}

private fun hasDuplicate(): Boolean {
return value.distinct() != value
}

private fun isSorted(): Boolean {
return value.map { it.value }
.zipWithNext { a, b -> a <= b }
Comment on lines +37 to +38
Copy link

Choose a reason for hiding this comment

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

LottoNumber가 Comparable을 구현하면 값을 꺼내지 않고도 구현할 수 있을 것 같습니다.

.all { it }
}

companion object {
fun of(numbers: List<Int>): LottoNumbers {
val lottoNumbers = numbers.map { LottoNumber(it) }

return LottoNumbers(lottoNumbers)
}
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/lotto/domain/LottoNumbersGenerateStrategy.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package lotto.domain

fun interface LottoNumbersGenerateStrategy {
fun generate(): LottoNumbers
}
16 changes: 12 additions & 4 deletions src/main/kotlin/lotto/domain/LottoStore.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package lotto.domain

private const val LOTTO_PRICE = 1000
class LottoStore {
private val lottoGenerator = LottoGenerator()

class LottoStore(private val lottoGenerator: LottoGenerator) {
fun buyLottos(money: Int): Lottos {
fun buyLotto(lottoNumbersGenerateStrategy: LottoNumbersGenerateStrategy): Lotto {
return lottoGenerator.publish(lottoNumbersGenerateStrategy)
}

fun buyLottos(money: Int, lottoNumbersGenerateStrategy: LottoNumbersGenerateStrategy): Lottos {
Comment on lines +6 to +10
Copy link

Choose a reason for hiding this comment

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

buyLotto는 수동 로또만, butLottos는 자동 로또만 만들고 있습니다.
전략을 주입받는 이유가 있을까요?

val lottoCount = money / LOTTO_PRICE
val lottos = List(lottoCount) {
lottoGenerator.publish()
lottoGenerator.publish(lottoNumbersGenerateStrategy)
}

return Lottos(lottos)
}

companion object {
const val LOTTO_PRICE = 1000
}
}
10 changes: 5 additions & 5 deletions src/main/kotlin/lotto/domain/Lottos.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package lotto.domain

@JvmInline
value class Lottos(val value: List<Lotto>) {
fun matchAll(winningLotto: WinningLotto): Lottos {
val lottos = value.map { it.match(winningLotto) }
fun matchAll(winningLotto: WinningLotto): MatchedLottos {
val matchedLottos = value.map { it.match(winningLotto) }

return Lottos(lottos)
return MatchedLottos(matchedLottos)
}

fun totalReward(): Int {
return value.sumOf { it.winning.reward }
operator fun plus(other: Lottos): Lottos {
return Lottos(value + other.value)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package lotto.domain

class ManualLottoNumbersGenerateStrategy(
private val lottoNumbers: List<Int>,
) : LottoNumbersGenerateStrategy {
override fun generate(): LottoNumbers {
return LottoNumbers.of(lottoNumbers)
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/lotto/domain/MatchedLotto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package lotto.domain

class MatchedLotto(
val lotto: Lotto,
val winning: LottoWinning,
)
8 changes: 8 additions & 0 deletions src/main/kotlin/lotto/domain/MatchedLottos.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package lotto.domain

@JvmInline
value class MatchedLottos(val value: List<MatchedLotto>) {
fun totalReward(): Int {
return value.sumOf { it.winning.reward }
}
}
6 changes: 1 addition & 5 deletions src/main/kotlin/lotto/domain/WinningLotto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ package lotto.domain

data class WinningLotto(
val lotto: Lotto,
val bonusNumber: Int,
val bonusNumber: LottoNumber,
) {
init {
require(!lotto.numbers.contains(bonusNumber)) {
"보너스 번호는 로또 번호에 포함되면 안됩니다."
}

require(bonusNumber in Lotto.MIN_NUMBER..Lotto.MAX_NUMBER) {
"보너스 번호는 항상 ${Lotto.MIN_NUMBER}에서 ${Lotto.MAX_NUMBER}사이 값이어야 합니다."
}
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/lotto/ui/InputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ object InputView {
return readln().toInt()
}

fun inputManualLottoCount(): Int {
println("수동으로 구매할 로또 수를 입력해 주세요.")

return readln().toInt()
}

fun inputManualLottoNumber(): List<Int> {
println("수동으로 구매할 번호를 입력해 주세요.")

return readln()
.split(",")
.map { it.toInt() }
.sorted()
}

fun inputWinningNumbers(): List<Int> {
println("지난 주 당첨 번호를 입력해 주세요.")

Expand Down
7 changes: 4 additions & 3 deletions src/main/kotlin/lotto/ui/ResultView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ package lotto.ui

import lotto.domain.LottoWinning
import lotto.domain.Lottos
import lotto.domain.MatchedLottos
import java.text.DecimalFormat

object ResultView {
fun showBoughtLottos(lottos: Lottos) {
println("${lottos.value.size}개를 구매했습니다.")
fun showBoughtLottos(lottos: Lottos, manualLottoCount: Int) {
println("수동으로 ${manualLottoCount}장 자동으로 ${lottos.value.size - manualLottoCount}개를 구매했습니다.")
Copy link

Choose a reason for hiding this comment

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

굳이 따져보자면 이런 빼기도 로직으로 볼 수 있을 것 같네요.
간단한 어플리케이션이니만큼 최대한 도메인 로직은 도메인에서 처리해보면 좋을 것 같아요.

lottos.value.forEach {
println("numbers = ${it.numbers}")
}
}

fun showResult(lottos: Lottos, returnRate: Double) {
fun showResult(lottos: MatchedLottos, returnRate: Double) {
val winningLottos = lottos.value
.groupBy { it.winning }

Expand Down
34 changes: 0 additions & 34 deletions src/test/kotlin/lotto/domain/DefaultLottoGenerateStrategyTest.kt

This file was deleted.

Loading