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

[Step2] 로또(자동) #601

Open
wants to merge 11 commits into
base: kminkyung
Choose a base branch
from
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# kotlin-lotto
# kotlin-lottery
2 changes: 1 addition & 1 deletion src/main/kotlin/calculator/StringAddCalculator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ class StringAddCalculator(
}

companion object {
const val DEFAULT_RESULT_VALUE = 0
private const val DEFAULT_RESULT_VALUE = 0
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/lottery/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 요구사항

## 로또(자동)
* 로또 1장의 가격은 1000원이다.

### 화면 로직
- [x] 로또 구입 금액을 입력받을 수 있다. ("구입금액을 입력해 주세요.")
- [x] 로또 구매 결과를 표시할 수 있다. ("14개를 구매했습니다.")
- [x] 지난 주 당첨 번호를 입력받을 수 있다. ("지난 주 당첨 번호를 입력해 주세요.")

### 비즈니스 로직
- [x] 구입 금액 입력시 해당하는 로또를 발급할 수 있다.
- [x] 로또는 6개의 숫자를 가진다.
- [x] 로또의 각 숫자는 1 이상 45 이하다.

- [x] 당첨 결과를 연산할 수 있다.
- [x] 당첨 결과를 표시할 수 있다. -> 당첨 통계
- [x] 총 수익률을 계산할 수 있다.
- [x] 총 수익률을 표시할 수 있다. "총 수익률은 0.35입니다."
44 changes: 44 additions & 0 deletions src/main/kotlin/lottery/controller/LotteryController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package lottery.controller

import lottery.domain.lotto.Lotto
import lottery.domain.winningresult.WinningResult
import lottery.service.CalculatorService
import lottery.service.LottoService
import lottery.service.WinningResultService
import lottery.ui.ViewService

class LotteryController(
private val viewService: ViewService,
private val lottoService: LottoService,
private val winningResultService: WinningResultService,
private val calculatorService: CalculatorService,
) {
fun run() {
val amount = viewService.getPurchasingAmount()
val lottos = purchaseLottos(amount)

val result = drawWinners(lottos)

reportRateOfReturn(amount, result)
}

private fun purchaseLottos(amount: Long): List<Lotto> {
return lottoService.issue(amount).also {
viewService.showPurchasingResult(it)
}
}

private fun drawWinners(lottos: List<Lotto>): WinningResult {
val winningNumber = viewService.getWinningNumber()
return winningResultService.draw(lottos, winningNumber)
.also {
viewService.showResultOfWinning(it)
}
}

private fun reportRateOfReturn(amount: Long, result: WinningResult) {
val rateOfReturn = calculatorService.rateOfReturn(amount, result)
viewService.showRateOfReturn(rateOfReturn)
}

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

class Lotto(
var numbers: List<Int> = emptyList()
Copy link

Choose a reason for hiding this comment

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

Suggested change
var numbers: List<Int> = emptyList()
val numbers: List<Int> = emptyList()

컬렉션은 외부에서 주솟값을 바꾸지 않아도 add, remove 함수를 통해 데이터를 조작할 수 있습니다.
컬렉션은 val을 활용해보면 어떨까요?

) {
init {
if (numbers.isNotEmpty()) {
check(numbers.size == 6)
Copy link

Choose a reason for hiding this comment

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

숫자 6은 어떤 것을 의미할까요?

} else {
createNumbers()
}
}

private fun createNumbers() {
numbers = numberRange.shuffled().subList(0, LOTTO_SLOT).sorted()
}
Comment on lines +14 to +16
Copy link

Choose a reason for hiding this comment

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

로또 번호가 중복되면 어떻게 될까요?


override fun toString() = "Lotto:$numbers"

companion object {
private const val LOTTO_SLOT = 6
private val numberRange = (1..45)
}
}
3 changes: 3 additions & 0 deletions src/main/kotlin/lottery/domain/ranking/NumberOfWins.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package lottery.domain.winningresult

typealias NumberOfWins = Int
Copy link

Choose a reason for hiding this comment

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

Int타입을 NumberOfWins로 타입의 이름을 변경해주셨네요.
다만, 지금처럼 사용하게 된다면 NumberOfWins외 다른 Int를 사용하는 경우와 햇갈릴 수 있습니다.
typealias 보다 value class를 활용해보면 어떨까요?

ref. https://quickbirdstudios.com/blog/kotlin-value-classes/

10 changes: 10 additions & 0 deletions src/main/kotlin/lottery/domain/ranking/Ranking.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package lottery.domain.ranking

import lottery.domain.winningresult.NumberOfWins

enum class Ranking(val rank: NumberOfWins, val prize: Int) {
THIRD(3, 5000),
FOURTH(4, 50000),
FIFTH(5, 1500000),
SIXTH(6, 2000000000)
}
3 changes: 3 additions & 0 deletions src/main/kotlin/lottery/domain/ranking/WinningResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package lottery.domain.winningresult

typealias WinningResult = Map<NumberOfWins, Int>
26 changes: 26 additions & 0 deletions src/main/kotlin/lottery/main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lottery

import lottery.controller.LotteryController
import lottery.service.CalculatorService
import lottery.service.ExchangeService
import lottery.service.LottoService
import lottery.service.WinningResultService
import lottery.ui.InputView
import lottery.ui.ResultView
import lottery.ui.ViewService

fun main() {
val viewService = ViewService(InputView(), ResultView())
val lottoService = LottoService(ExchangeService())
val winningResultService = WinningResultService()
val calculatorService = CalculatorService()

val lotteryController = LotteryController(
viewService,
lottoService,
winningResultService,
calculatorService
)

lotteryController.run()
}
22 changes: 22 additions & 0 deletions src/main/kotlin/lottery/service/CalculatorService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package lottery.service

import lottery.domain.ranking.Ranking
import lottery.domain.winningresult.WinningResult
import java.math.RoundingMode
import java.text.DecimalFormat

class CalculatorService {
fun rateOfReturn(amount: Long, result: WinningResult): String {
val earning = Ranking.values().sumOf {
result[it.rank]!!.times(it.prize)
}
Comment on lines +10 to +12
Copy link

Choose a reason for hiding this comment

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

CalculatorService 객체에서 Ranking 객체에 직접 접근해서 계산을 해주고 있네요.
만약 Ranking에 값이 추가가 되거나 제거가 된다면, Ranking 클래스 뿐만 아니라 CalculatorService에도 영향을 미치게 됩니다.
Ranking 클래스에 메시지를 보내서, Ranking이 변경되더라도 CalculatorService에는 영향을 미치지 않도록 코드를 개선해보면 어떨까요?


return decimalFormat.format(earning.toDouble().div(amount.toDouble()))
}

companion object {
private val decimalFormat = DecimalFormat("#.##").also {
it.roundingMode = RoundingMode.DOWN
}
}
}
14 changes: 14 additions & 0 deletions src/main/kotlin/lottery/service/ExchangeService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package lottery.service

import kotlin.math.floor

class ExchangeService {
fun calculateQuantity(amount: Long): Int {
check(amount > LOTTO_PRICE)
return floor(amount.toDouble().div(LOTTO_PRICE)).toInt()
}

companion object {
const val LOTTO_PRICE = 1000
}
}
13 changes: 13 additions & 0 deletions src/main/kotlin/lottery/service/LottoService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package lottery.service

import lottery.domain.lotto.Lotto

class LottoService(
private val exchangeService: ExchangeService
){

fun issue(amount: Long): List<Lotto> {
val quantity = exchangeService.calculateQuantity(amount)
return List(quantity) { Lotto() }
}
}
26 changes: 26 additions & 0 deletions src/main/kotlin/lottery/service/WinningResultService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lottery.service

import lottery.domain.lotto.Lotto
import lottery.domain.ranking.Ranking
import lottery.domain.winningresult.WinningResult

class WinningResultService {
fun draw(lottos: List<Lotto>, winningNumbers: List<Int>): WinningResult {
val result = prepareResult()

lottos.forEach { lotto ->
val numberOfWins = lotto.numbers.filter { winningNumbers.contains(it) }.size
result[numberOfWins] = result[numberOfWins]?.plus(1) ?: return@forEach
}

return result
}

private fun prepareResult(): MutableMap<Int, Int> {
val result = mutableMapOf<Int, Int>()
Ranking.values().forEach { result[it.rank] = 0 }

return result
}

}
21 changes: 21 additions & 0 deletions src/main/kotlin/lottery/ui/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package lottery.ui

class InputView {
fun getPurchasingAmount(): Long {
println(PURCHASING_AMOUNT_MESSAGE)
return readln().toLong()
}

fun getWinningNumber(): List<Int> {
println(GET_WINNING_NUMBER_MESSAGE)
return readln()
.split(DELIMITER)
.map { it.toInt() }
}

companion object {
private const val PURCHASING_AMOUNT_MESSAGE = "구입금액을 입력해 주세요."
private const val GET_WINNING_NUMBER_MESSAGE = "지난 주 당첨 번호를 입력해 주세요."
private const val DELIMITER = ","
}
}
38 changes: 38 additions & 0 deletions src/main/kotlin/lottery/ui/ResultView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package lottery.ui

import lottery.domain.lotto.Lotto
import lottery.domain.ranking.Ranking
import lottery.domain.winningresult.WinningResult

class ResultView {
fun showPurchasingResult(lottos: List<Lotto>) {
println("${lottos.size}개를 구매했습니다.")
showIssuedLottos(lottos)
}

private fun showIssuedLottos(lottos: List<Lotto>) {
lottos.forEach {
println(it.numbers)
}
println("\n")
}

fun showResultOfWinning(winningResult: WinningResult) {
val resultByRank = Ranking.values().map {
"${it.rank}개 일치 (${it.prize}원) - ${winningResult[it.rank]}개"
}.joinToString("\n")

val statistics = buildString {
append("당첨 통계\n")
append("---------\n")
append(resultByRank)
}

println(statistics)
}

fun showRateOfReturn(rateOfReturn: String) {
val totalRateOfReturnMessage = "총 수익률은 ${rateOfReturn}입니다."
println(totalRateOfReturnMessage)
}
}
30 changes: 30 additions & 0 deletions src/main/kotlin/lottery/ui/ViewService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package lottery.ui

import lottery.domain.lotto.Lotto
import lottery.domain.winningresult.WinningResult

class ViewService(
private val inputView: InputView,
private val resultView: ResultView,
) {

fun getPurchasingAmount(): Long {
return inputView.getPurchasingAmount()
}

fun getWinningNumber(): List<Int> {
return inputView.getWinningNumber()
}

fun showPurchasingResult(lottos: List<Lotto>) {
return resultView.showPurchasingResult(lottos)
}

fun showResultOfWinning(result: WinningResult) {
return resultView.showResultOfWinning(result)
}

fun showRateOfReturn(rateOfReturn: String) {
return resultView.showRateOfReturn(rateOfReturn)
}
}
20 changes: 20 additions & 0 deletions src/test/kotlin/lottery/domain/lotto/LottoSpec.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package lottery.domain.lotto

import io.kotest.core.spec.style.StringSpec
import io.kotest.inspectors.shouldForAll
import io.kotest.matchers.collections.shouldHaveSize

class LottoSpec : StringSpec({
val lotto = Lotto()

"로또는 6개의 숫자를 가진다" {
lotto.numbers shouldHaveSize 6
}

"로또의 각 숫자는 1 이상 45 이하다" {
lotto.numbers.shouldForAll {
it in 1..45
}
}

})
27 changes: 27 additions & 0 deletions src/test/kotlin/lottery/service/CalculatorServiceSpec.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package lottery.service

import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe

class CalculatorServiceSpec : BehaviorSpec({

given("수익률 계산 서비스는") {
val calculatorService = CalculatorService()

When("구입 금액과 로또 결과로부터") {
val amount = 14000L
val winningResult = mapOf(
3 to 1,
4 to 0,
5 to 0,
6 to 0
)

val rateOfReturn = calculatorService.rateOfReturn(amount, winningResult)

Then("수익률을 계산하여 반환한다") {
rateOfReturn shouldBe "0.35"
}
}
}
})
Loading