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

[Step3] 로또(2등) 구현 #1084

Merged
merged 9 commits into from
Nov 28, 2024
18 changes: 0 additions & 18 deletions src/main/kotlin/lotto/LottoSystem.kt

This file was deleted.

15 changes: 11 additions & 4 deletions src/main/kotlin/lotto/Main.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package lotto

import lotto.service.OrderService
import lotto.service.WinningLottoService
import lotto.view.InputView
import lotto.view.ResultView

fun main() {
val lottoSystem = LottoSystem()
val orderService = OrderService()
val winningLottoService = WinningLottoService()

val amount = InputView.getAmount()
val order = lottoSystem.createOrder(amount)
val order = orderService.makeOrder(amount)
ResultView.printCreatedLottos(order.lottos)

val winNumberInput = InputView.getWinNumberInput()
val winNumbers = lottoSystem.createWinNumbers(winNumberInput)
val bonusNumber = InputView.getBonusNumber()
val winNumbers = winningLottoService.createWinningLotto(winNumberInput, bonusNumber)

val result = lottoSystem.createWinningResult(order, winNumbers)
val result = winningLottoService.checkAndGetResult(order, winNumbers)
ResultView.printResult(result)
}
27 changes: 0 additions & 27 deletions src/main/kotlin/lotto/Order.kt

This file was deleted.

18 changes: 0 additions & 18 deletions src/main/kotlin/lotto/Prize.kt

This file was deleted.

10 changes: 0 additions & 10 deletions src/main/kotlin/lotto/RankResult.kt

This file was deleted.

18 changes: 0 additions & 18 deletions src/main/kotlin/lotto/ResultView.kt

This file was deleted.

49 changes: 0 additions & 49 deletions src/main/kotlin/lotto/WinningResult.kt

This file was deleted.

12 changes: 12 additions & 0 deletions src/main/kotlin/lotto/const/LottoConst.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package lotto.const

import lotto.domain.LottoNumber

object LottoConst {

Choose a reason for hiding this comment

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

현재 공통으로 사용되는 상수값들을 LottoConst라는 object를 만들어서 모아두고 사용중입니다. 남재님은 상수값들이 여러곳에서 필요할때 어떻게 처리하시는지 궁금합니다!

저는 개인적으로 Const같은 상수 집합을 만드는것을 선호하지는 않아요!
프로젝트가 커지다보면, 상수가 많아져서 관리가 힘들기도 하고
관리의 범위가 애매해지는것 같아요 :)

LottoConst라는 상수집합을 만들기보단, 상수값은 보통 책임있는곳에서 정의하는 편 입니다!
UNIT_OF_AMOUNT로 선언하기 보단 Lotto.Price LottoShop.Price 와 같이 좀더 상수가 명확해지는 효과도 노릴수 있어요!
추가적으로 LOTTO_NUMBERS 관련된 기능은 Creator 객체의 책임은 아닐까요?

const val UNIT_OF_AMOUNT = 1000
val LOTTO_NUMBERS = IntRange(1, 45).map { LottoNumber(it) }.toList()

fun getLottoNumber(number: Int): LottoNumber {
return requireNotNull(LOTTO_NUMBERS.find { it == LottoNumber(number) }) { "일치하는 번호의 로또번호가 존재하지 않습니다." }
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
package lotto

class Lotto(generatedNumbers: Set<Int>) {
val numbers: Set<LottoNumber>
package lotto.domain

class Lotto(val numbers: Set<LottoNumber>) {
init {
validateSize(generatedNumbers)
numbers = generatedNumbers.map { LottoNumber(it) }.toSet()
}

fun countMatchingNumbers(targetLotto: Set<LottoNumber>): Int {
return targetLotto.count { it in this.numbers }
validateSize(numbers)
}

private fun validateSize(numbers: Set<Int>) {
private fun validateSize(numbers: Set<LottoNumber>) {
require(numbers.size == LOTTO_NUMBER_SIZE) { "로또 번호는 ${LOTTO_NUMBER_SIZE}개여야 합니다. 현재 전달된 개수는 ${numbers.size}개 입니다." }
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package lotto
package lotto.domain

@JvmInline
value class LottoNumber(private val number: Int) {
Expand All @@ -10,6 +10,10 @@ value class LottoNumber(private val number: Int) {
require(this.number in LOTTO_RANGE) { "로또 번호는 ${LOTTO_RANGE.first} ~ ${LOTTO_RANGE.last} 내의 숫자여야 합니다." }
}

override fun toString(): String {
return "$number"
}

companion object {
private val LOTTO_RANGE = 1..45
}
Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/lotto/domain/LottoResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package lotto.domain

data class LottoResult(
val totalCount: Int,
val rank: Rank,
) {
Comment on lines +3 to +6

Choose a reason for hiding this comment

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

로또의 결과보다는 로또의 통계를 나타내는건 아닐까요 ?:)

fun getTotalPrizeMoney(): Int {
return this.rank.prizeAmount * totalCount
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/lotto/domain/Order.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package lotto.domain

import lotto.const.LottoConst.UNIT_OF_AMOUNT

data class Order(
val amount: Int,
val lottos: List<Lotto>,
) {
init {
validateLottoCounts()
}

private fun validateLottoCounts() {
val count = amount / UNIT_OF_AMOUNT
require(lottos.size == count) { "구매한 금액과 로또의 수량이 일치하지 않습니다." }
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/lotto/domain/Rank.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package lotto.domain

enum class Rank(
val matchCount: Int,
val prizeAmount: Int,
private val match: (Int, Boolean) -> Boolean,
) {
FIRST(6, 2_000_000_000, { count, _ -> count == 6 }),
SECOND(5, 30_000_000, { count, isBonusMatch -> count == 5 && isBonusMatch }),
THIRD(5, 1_500_000, { count, _ -> count == 5 }),
FOURTH(4, 50_000, { count, _ -> count == 4 }),
FIFTH(3, 5_000, { count, _ -> count == 3 }),
MISS(0, 0, { count, _ -> count < 3 }), ;

companion object {
fun findByMatchCount(
matchCount: Int,
isBonusMatch: Boolean = false,
): Rank {
return entries.firstOrNull { it.match(matchCount, isBonusMatch) }
?: throw RuntimeException("일치하는 숫자의 개수에 해당하는 상품이 존재하지 않습니다.")
Comment on lines +20 to +21

Choose a reason for hiding this comment

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

에러를 던지는것보다 null 이나 MISS 를 리턴함으로써
에러를 대체할수 있어요 :)
실제로 코틀린에서는 에러를 던지기보단 null을 반환하기도 해요!

Copy link
Author

@Seokho-Ham Seokho-Ham Nov 27, 2024

Choose a reason for hiding this comment

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

음 이건 궁금증이 생기는 부분이네요!
null을 반환하면 해당 결과값을 받는 코드에서는 nullable하게 처리가 될거 같습니다.
그럼 접근할때 NullPointException이 발생하는 경우가 발생할수도 있을텐데 예외를 던지는것보다 null로 던지는게 권장되는걸까요?! 실전에서는 이런 부분에 대해서는 보통 어떻게 처리를 하나요?

추가적으로 이번 과정을 하면서 예외를 명시적으로 던지는것을 권장하지 않는듯한 느낌을 받았는데 혹시 이유가 있을까요?!
(코틀린을 처음 사용해봐서 아직 모르는게 많아서 질문드립니다ㅎㅎ)

Choose a reason for hiding this comment

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

코틀린에서는 Checked Exception를 강제하지 않는 등의 이유로
예외를 명시적으로 던지는걸 권장하지는 않아요
경우에 따라 Null이나 sealed 클래스(ex> Result)를 많이 사용하곤 해요,

코틀린에서는 nullable인 경우에도 ?. , ?: 같은 키워드를 사용해서 nullSafe한 방법을 많이 활용하긴합니다!

}
}
}
14 changes: 14 additions & 0 deletions src/main/kotlin/lotto/domain/WinningLotto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package lotto.domain

class WinningLotto(
val winningNumbers: Lotto,
val bonusNumber: LottoNumber,
) {
Comment on lines +3 to +6

Choose a reason for hiding this comment

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

bonusNumber와 winningNumbers 중첩되는 상황도 고려하면 좋을거같아요 :)

fun countMatchingNumbers(targetLotto: Lotto): Int {
return targetLotto.numbers.count { it in winningNumbers.numbers }
}

fun matchBonusNumber(targetLotto: Lotto): Boolean {
return targetLotto.numbers.contains(bonusNumber)
}
Comment on lines +14 to +20

Choose a reason for hiding this comment

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

외부에서 사용되지 않는 함수는 private으로 하면 어떨까요 :)

}
28 changes: 28 additions & 0 deletions src/main/kotlin/lotto/service/LottoCreator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package lotto.service

import lotto.const.LottoConst
import lotto.domain.Lotto
import lotto.domain.LottoNumber
import lotto.domain.WinningLotto
import lotto.util.NumberGenerator
import lotto.util.RandomNumberGenerator

class LottoCreator(private val numberGenerator: NumberGenerator = RandomNumberGenerator()) {
fun createLottos(count: Int): List<Lotto> {
return List(count) { createSingleLotto() }
}

fun createWinningLotto(
winningNumbers: Set<Int>,
bonusNumber: Int,
): WinningLotto {
val winningLotto = Lotto(winningNumbers.map { LottoNumber(it) }.toSet())
return WinningLotto(winningLotto, LottoConst.getLottoNumber(bonusNumber))
}

private fun createSingleLotto(): Lotto {
val randomNumbers = numberGenerator.generate()
val lotto = Lotto(randomNumbers.map { LottoConst.LOTTO_NUMBERS[it - 1] }.toSet())
return lotto
}
}
23 changes: 23 additions & 0 deletions src/main/kotlin/lotto/service/OrderService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package lotto.service

import lotto.const.LottoConst.UNIT_OF_AMOUNT
import lotto.domain.Order

class OrderService() {

Choose a reason for hiding this comment

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

Service는 Spring의 레이어를 의미하는 접미사이고,
사실 애매한 네이밍인거 같아요
현실세계의 사물을 객체화해보면 어떨까요?
로또머신, 로또가게 등의 사물을 객체화한 네이밍은 어떨까요?

Copy link
Author

@Seokho-Ham Seokho-Ham Nov 27, 2024

Choose a reason for hiding this comment

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

�그런거 같네요! 바로 수정하겠습니다😀

private val lottoCreator = LottoCreator()

Choose a reason for hiding this comment

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

내부 프로퍼티로 선언하기보단,
생성시점 주입하는 구조는 어떤 장점이 있을까요?

Copy link
Author

@Seokho-Ham Seokho-Ham Nov 27, 2024

Choose a reason for hiding this comment

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

생각해보니 스프링의 의존성 주입처럼 생성자를 통해서 주입해주는게 좋을거같네요!

  • 해당 클래스를 테스트하기 쉬워진다?
  • 결합도가 낮아져서 유연해진다?

생각나는 장점들은 이정도인것 같습니다!


fun makeOrder(amount: Int): Order {
validateAmountIsPositive(amount)
val lottoCounts = calculateLottoCounts(amount)
return Order(amount, lottoCreator.createLottos(lottoCounts))
}

private fun validateAmountIsPositive(amount: Int) {
require(amount > 0) { "로또 구매 금액은 음수이거나 0원일 수 없습니다. (현재 입력 금액: $amount)" }
}

private fun calculateLottoCounts(amount: Int): Int {
require(amount % UNIT_OF_AMOUNT == 0) { "로또 구매 금액은 1000원 단위로 입력되어야 합니다. (현재 입력 금액: $amount)" }
return amount / UNIT_OF_AMOUNT
}
}
Loading