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] 4단계 - 블랙잭(베팅) #697

Open
wants to merge 19 commits into
base: bong6981
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ed22ad5
[step4] docs: 기능 목록 정리
bong6981 Dec 2, 2023
ea2d901
[step4] feat: 21점이면 더이상 카드를 받지 못하도록 변경
bong6981 Dec 2, 2023
d095226
[step3] chore: getDesiredAction -> desiredAction 네이밍 변경
bong6981 Dec 2, 2023
b1ebc8a
[step4] feat: 플레이어 이름을 식별자로 사용
bong6981 Dec 2, 2023
158af44
[step4] feat: 플레이어별 베팅 금액 저장
bong6981 Dec 2, 2023
c8eca31
[step4] feat: 베팅 금액에 따른 수익 계산 및 출력
bong6981 Dec 3, 2023
3d30c05
[step3] refactor: View를 보여줄지 말지는 ViewResultProcessor에서 처리
bong6981 Dec 3, 2023
5181cef
[step3] refactor: requireNotNul()을 활용
bong6981 Dec 3, 2023
fc1d9ae
[step3] refactor: step3 피드백 반영
bong6981 Dec 3, 2023
a4db3db
[step4] chore: 생성 로직을 앞단으로 이동
bong6981 Dec 6, 2023
780067a
[step4] feat: PlayerGameResult 의 player 접근제어자 -> private
bong6981 Dec 6, 2023
d95162a
[step4] test: DCI 패턴을 고려한 테스트 이름 수정
bong6981 Dec 6, 2023
00f52f0
[step4] feat: 게임 승패는 BlackJackJudge에서 판단하도록 변경
bong6981 Dec 6, 2023
8bbefe0
[step4] feat: 블랙잭 여부는 BlackJackJudge에서 판단하도록 변경
bong6981 Dec 6, 2023
f96543d
[step4] chore: BUST_THRESHOLD 대신 BLACK_JACK_SCORE 사사용
bong6981 Dec 6, 2023
ebae7ee
[step4] feat: BetAmount 분리
bong6981 Dec 31, 2023
c66f8bf
[step4] chore: 가독성을 위한 코드 줄바꿈 변경
bong6981 Dec 31, 2023
02e8ce0
[step4] chore: 불필요한 let 사용 삭제
bong6981 Dec 31, 2023
25c8735
[step4] refactor: 의미 없는 BUST_SCORE 사용 삭제
bong6981 Dec 31, 2023
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
118 changes: 80 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,87 @@
- git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다.

### 기능 목록
Copy link
Member

Choose a reason for hiding this comment

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

문서화 👍

게임 입력
- [x] 게임에 참여할 사람의 이름을 입력 받는다
InputView : 게임에 필요한 입력값을 입력 받는다
- 게임에 참여할 사람의 이름을 입력 받는다
- 쉼표 기준으로 분리한다
- 2명의 이름을 입력 받는다
- 참가자의 이름은 공백일 수 없다.
게임 세팅
- [x] 입력 받은 사람의 이름으로 참가자를 생성한다
- [x] 카드 52장 (1덱)을 세팅한다
- [x] 플레이어는 게임을 시작할 때 베팅 금액을 정해야 한다

InputProcessor : 입력 금액을 검증하고 도메인 레벨로 변환한다
- 게임에 참여할 사람의 이름을 처리한다
- 게임 참여자는 2명이여야 한다
- 게임 참여자의 이름은 서로 달라야 한다
- 게임 참여자 이름은 공백일 수 없다.
- [x] 베팅 금액을 처리한다
- 베팅 금액은 0보다 커야 한다

BlackJackGame : 게임을 진행한다
- distributor 가 dealEnd 가 될 때까지 카드 배분을 진행한다
- 각 카드 배분의 결과를 result 처리기에 넘긴다
- 초기 카드 배분자는 DealIntialCards()이다
- 카드 배분 횟수를 초과하면 종료시킨다
- 한 사람이 최대 가질 수 있는 카드 수 11장 ( Ace(1) * 4 + 2 * 4 + 3 * 3) : 배분 9번 진행
- 한 게임당 가질 수 있는 최대 카드 배분 횟수 : 초키 카드 배분 + 9번 진행 * 2 + 딜러 카드 배분 = 20

GameTable
- 딜러와 플레이어를 갖고 상호간의 카드 이동을 담당한다
- 배팅 보드를 가지고 게임이 끝나면 게임 결과로 베팅 결과를 산출한다

Players
- 플레이어를 관리하고 어떤 플레이어의 턴인지 관리한다
- 플레이어 이름으로 플레이어를 생성한다
- PlayerNames : 플레이어 이름 리스트
- [x] 각 플레이어는 다른 이름이어야 함

Dealer
- 카드 52장 (1덱)을 세팅한다
- 스페이스, 하트, 다이아, 클로버 4개의 각 문양이 2~10, jack, queen, king, ace 13장으로 구성
게임 진행
- 처음 카드 배분
- [x] 참가자와 딜러에게 각각 2장의 카드를 나눠 준다
- [x] 받은 카드를 출력한다
- 딜러의 카드는 한 장만 출력한다
- 플레이어 카드 배분
- [x] 참가자 중 1명에게 카드를 더 받을지 묻고, 참가자는 y 또는 n 으로 대답한다.
- [x] 참가자가 y를 입력하면 카드 한 장을 더 지급한다
- [x] 한 사람이 받은 카드를 계산했을 때 21을 넘는다면 더이상 카드를 받을 수 없다.
- [x] 참가자가 n을 입력하면 다른 참가자에게 턴이 넘어간다. 두번째 참가자라면 딜러에게 턴이 넘어간다
- [x] 참가자가 y, n을 입력하면 카드 지급 여부에 관계 없이 결과를 출력한다
- 딜러 카드 배분
- [x] 처음 받은 2장의 카드의 합이 16이하이면 1장의 카드를 받고, 17이상이면 카드를 받지 않는다
- [x] 딜러가 카드를 받았는지 여부를 출력한다
- 결과 출력
- [x] 게임이 종료되면 딜러와 플레이어의 카드 점수를 출력한다
- [x] 게임 최종 결과는 21을 넘지 않으면서, 21에 가까운 방식으로 계산한다
- 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다.
- 승패 계산
- [x] 승패를 가른다
- 각 플레이어와 딜러의 승패를 가른다
- 버스트 : 21점 초과 / 푸시 : 딜러와 동점 (무승부)
- 딜러가 각각 딜러와 다 겨뤄 21에 더 가까운 사람이 승리한다
- 버스트시에는 점수가 0점이다
- 플레이어와 딜러가 둘 다 버스트라면 딜러가 승리한다
- 딜러와 플레이어가 버스트가 아니면서 동일 점수라면 무승부
- 가른 승패의 결과를 출력한다
- 게임 종료
- [x] 게임을 종료시킨다
- 카드 배분이 끝나면 (DistributionEnd) 게임을 종료시킨다
- 카드 배분 횟수를 초과하면 종료시킨다
- 한 사람이 최대 가질 수 있는 카드 수 11장 ( Ace(1) * 4 + 2 * 4 + 3 * 3) : 배분 9번 진행
- 한 게임당 가질 수 있는 최대 카드 배분 횟수 : 초키 카드 배분 + 9번 진행 * 2 + 딜러 카드 배분 = 20
- 자신의 카드 보유에 따라 hit or stand 를 반환한다
- 처음 받은 2장의 카드의 합이 16이하이면 1장의 카드를 받고, 17이상이면 카드를 받지 않는다

Player
- [x] 플레이어는 이름이 같으면 동일한 플레이어로 취급된다
- hitOrStand: 사용자의 의견에 따라 카드를 더 받을지(hit) 그만할지(stand)를 반환한다
- 사용자가 hit 를 요청했더라도 카드 점수가 21점 이상이면 stand 를 반환한다
- 딜러와의 점수를 비교해서 승패를 가를 수 있다
- 버스트 : 딜러 점수와 상관 없이 패
- 그 외 딜러보다 점수가 높으면 승, 같으면 무, 낮으면 패

BettingBoard : 베팅 보드
- [x] 플레이어별 베팅 금액을 저장
- [x] 게임이 종료되면 플레이어별 승패 결과에 따라 각 플레이어가 받을 금액을 결정한다
- 플레이어가 이겼을 때
- 블랙잭으로 이겼을 때 : 베팅금액 회수 + 베팅금액 * 1.5
- 일반 수로 이겼을 때 : 베팅금액 회수 + 베팅금액 * 1
- 무승부일 때 : 베팅금액 회수
- 졌을 때: 베팅금액 뺏김
- PlayerBet
- 플레이어의 베팅 금액과 받은 금액으로 최종 수익을 계산한다

DealInitialCards : 처음 카드 배분
- 참가자와 딜러에게 각각 2장의 카드를 나눠 준다

DealToPlayer
- 참가자 중 1명에게 카드를 더 받을지 묻고, 결과에 따라 카드를 지급한다
- 참가자가 hit 라면 카드 한 장을 더 지급한다
- 참가자가 stand 라면 다른 참가자에게 턴이 넘어간다. 두번째 참가자라면 딜러에게 턴이 넘어간다
- 참가자의 hit or stand 마다 배분 결과를 반환한다
- 참가자가 stand 였을 때 점수가 21점 이상이라면 시스템으로 인한 stand로 판별한다

DealToDealer
- 딜러의 카드 상태에 따라 hit or stand 를 조회하고 결과에 따라 카드를 지급한다
- 딜러까지 카드 배분이 끝나면 카드 배분을 종료한다

ResultProcessor
- 발생한 Result 이벤트에 따라 해당 이벤트를 알맞은 ResultHandler 에 전달한다
- DealToPlayerResult 인 경우 시스템상의 stand 라면 뷰에 결과를 전달하지 않는다

ViewResultProcessor
- 딜러의 카드는 한 장만 출력한다
- 게임이 종료되면 (GameResult 인 경우) 딜러와 플레이어의 카드 점수를 출력한다
- 베팅 결과를 최종 수익을 출력한다

GameResult
- 게임 최종 결과는 21을 넘지 않으면서, 21에 가까운 방식으로 계산한다
- 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다.
3 changes: 3 additions & 0 deletions src/main/kotlin/blackjack/controller/InputProcessor.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package blackjack.controller

import blackjack.domain.Action
import blackjack.domain.batting.Amount
import blackjack.domain.player.Player
import blackjack.domain.player.PlayerNames

interface InputProcessor {
fun playerNames(): PlayerNames

fun playerBet(player: Player): Amount

fun playerAction(player: Player): Action
}
11 changes: 5 additions & 6 deletions src/main/kotlin/blackjack/controller/ResultProcessor.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
package blackjack.controller

import blackjack.domain.result.Result
import blackjack.domain.result.distribution.DealEndResult
import blackjack.domain.result.distribution.DealInitialCardResult
import blackjack.domain.result.distribution.DealToDealerResult
import blackjack.domain.result.distribution.DealToPlayerResult
import blackjack.domain.result.game.GameResult
import blackjack.domain.result.game.GameEndResult

class ResultProcessor {
fun handle(result: Result) {
when (result) {
is DealInitialCardResult -> ViewResultProcessor.drawInitialDistribution(result)
is DealToPlayerResult -> {
if (result.isSystemStand) return
ViewResultProcessor.drawPlayerState(result)
}
is DealToPlayerResult -> ViewResultProcessor.drawPlayerState(result)
is DealEndResult -> {}
is DealToDealerResult -> ViewResultProcessor.drawDealerState(result)
is GameResult -> ViewResultProcessor.drawGameResult(result)
is GameEndResult -> ViewResultProcessor.drawGameResult(result)
}
}
}
9 changes: 8 additions & 1 deletion src/main/kotlin/blackjack/controller/ViewInputProcessor.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package blackjack.controller

import blackjack.domain.Action
import blackjack.domain.batting.Amount
import blackjack.domain.player.Player
import blackjack.domain.player.PlayerNames
import blackjack.view.dto.PlayerNameDto
Expand All @@ -10,11 +11,17 @@ class ViewInputProcessor : InputProcessor {
override fun playerNames(): PlayerNames =
InputView.playerNames().let(PlayerNames::from)

override fun playerBet(player: Player): Amount =
InputView.playerBet(player.nameDto())
.let { Amount.betAmount(it.toInt()) }

override fun playerAction(player: Player): Action {
val action = InputView.playerAction(player.let(PlayerNameDto::from))
val action = InputView.playerAction(player.nameDto())
return toPlayerAction(action)
}

private fun Player.nameDto(): PlayerNameDto = this.let(PlayerNameDto::from)
Copy link
Member

Choose a reason for hiding this comment

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

let에 생성자 제가 조금 설명을 러프하게 드렸었는데요. 생성자를 사용해도 되는 것에 대해서 굳이 let을 사용하지 않는게 좋을 것 같다라는 것이 제 의견이었습니다.

this.let(PlayerNameDto::from)
PlayerNameDto.from(this)

에서 읽히는 과정을 본다면 let은 this를 let으로 처리하는데 이때 from을 사용하여 생성한다가 될 것이고, 아래는 this를 from을 사용하여 생성한다로 읽히게 되어요. 결국 let은 추가적인 재처리를 위한 용도로 사용되는데 생성자를 재처리하는 것이 로직적인 문제는 없으나 의미가 없는 tailing 구문이 발생하는 것과 다름이 없으므로 큰 복잡한 로직 없이 생성을 한다면 let을 제거하는 것이 좋다라는 생각이에요.

Copy link
Author

Choose a reason for hiding this comment

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

음.. 제가 아직 명확하게 이해하지 못한 것 같습니다.

  • 말씀주신 예시에서는 let 을 쓰지 않는 것이 더 가독성도 좋아 보입니다.

만약, 아래 예시에서는

InputParser.intoPlayerNames(readInput())
readInput().let(InputParser::intoPlayerNames)
  • 아래 구문이 개인적으로는 가독성이 더 좋아보입니다. 이 역시 불필요한 tailing 구문으로 볼 수 있긴 하겠지만, 왼쪽에서 오른쪽으로 읽을 경우 input 을 읽어와서 파싱한다로 좀 더 자연스럽지 않나 라는 생각이 드는데요.
  • 혹시 이런 경우에도 let 사용을 지양하시는 것일까요?
 return when {
                ranks.hasAce().not() -> score
                score + ACE_BONUS_SCORE > MAX_SCORE -> score
                else -> score + ACE_BONUS_SCORE
            }.let(::HandScore)

이런 케이스에서도 HandScore 을 생성하다는 로직 보다는 어떻게 점수 계산이 되는지 when { } 안의 로직이 먼저 읽혀야 한다고 생각해서 여기는 let 사용이 낫지 않나 라는 생각이 듭니다!

그러나 위 2 케이스 모두 리뷰어님이 말씀주신 큰 복잡한 로직 없이 생성하는 로직인 것 같습니다. 이에 대해서도 같은 생각이신지 궁금합니다!


private fun toPlayerAction(action: String): Action = when (action) {
"y" -> Action.HIT
"n" -> Action.STAND
Expand Down
38 changes: 26 additions & 12 deletions src/main/kotlin/blackjack/controller/ViewResultProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package blackjack.controller
import blackjack.domain.result.distribution.DealInitialCardResult
import blackjack.domain.result.distribution.DealToDealerResult
import blackjack.domain.result.distribution.DealToPlayerResult
import blackjack.domain.result.game.GameResult
import blackjack.domain.result.game.GameEndResult
import blackjack.view.dto.DealerCardsResultDto
import blackjack.view.dto.DealerHitDto
import blackjack.view.dto.FinalDealerStateDto
import blackjack.view.dto.FinalPlayerStateDto
import blackjack.view.dto.DealerProfitDto
import blackjack.view.dto.PlayerCardsResultDto
import blackjack.view.dto.PlayerDto
import blackjack.view.dto.PlayerProfitDto
import blackjack.view.output.OutputView

object ViewResultProcessor {
Expand All @@ -18,6 +20,7 @@ object ViewResultProcessor {
}

fun drawPlayerState(result: DealToPlayerResult) {
if (result.isSystemStand) return
val dto = result.player.let { PlayerDto(it.name.value, it.hand.cards) }
OutputView.drawPlayerCurrentState(dto)
}
Expand All @@ -26,15 +29,26 @@ object ViewResultProcessor {
OutputView.drawDealerHitStatus(DealerHitDto(result.isHit))
}

fun drawGameResult(result: GameResult) {
fun drawGameResult(result: GameEndResult) {
drawCardResult(result)
drawProfitResult(result)
}

private fun drawCardResult(result: GameEndResult) {
val dealerDto =
DealerCardsResultDto(result.dealerHand.cards, result.dealerScore.value)
val playersDto = result.playerResults.map {
PlayerCardsResultDto(it.name.value, it.hand.cards, it.score.value)
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
val dealerDto =
DealerCardsResultDto(result.dealerHand.cards, result.dealerScore.value)
val playersDto = result.playerResults.map {
PlayerCardsResultDto(it.name.value, it.hand.cards, it.score.value)
}
val dealerDto = DealerCardsResultDto(result.dealerHand.cards, result.dealerScore.value)
val playersDto = result.playerResults
.map { PlayerCardsResultDto(it.name.value, it.hand.cards, it.score.value) }

사소한 의견이지만, 개인적으로 이렇게 처리하는 것이 가독성이 더 좋을 것 같네요.

Copy link
Author

Choose a reason for hiding this comment

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

이를 반영했습니다!

OutputView.drawCardsResults(dealerDto, playersDto)
}

private fun drawProfitResult(result: GameEndResult) {
val dealerDto =
result.dealerResults.let {
FinalDealerStateDto(it.dealer.hand.cards, it.dealer.score.cardScore, it.status)
}
val playersDto =
result.playersResult.map {
FinalPlayerStateDto(it.player.name.value, it.player.hand.cards, it.player.score.cardScore, it.status)
}
OutputView.drawFinalResults(dealerDto, playersDto)
DealerProfitDto(result.dealerProfit.value.toString())
val playersDto = result.playerResults.map {
PlayerProfitDto(it.name.value, it.profit.value.toString())
}
OutputView.drawProfitResults(dealerDto, playersDto)
}
}
24 changes: 14 additions & 10 deletions src/main/kotlin/blackjack/domain/BlackJackGame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@ package blackjack.domain

import blackjack.controller.InputProcessor
import blackjack.controller.ResultProcessor
import blackjack.domain.batting.BetBoard
import blackjack.domain.distirbution.CardDistributor
import blackjack.domain.distirbution.DealEnd
import blackjack.domain.distirbution.DealInitialCards
import blackjack.domain.player.Players
import blackjack.domain.result.Result
import blackjack.domain.result.distribution.DealEndResult
import blackjack.domain.result.game.GameEndResult

class BlackJackGame(
private val inputProcessor: InputProcessor,
private val resultProcessor: ResultProcessor = ResultProcessor(),
inputProcessor: InputProcessor,
players: Players = Players.of(inputProcessor.playerNames()) { player -> inputProcessor.playerAction(player) },
private val resultProcessor: ResultProcessor,
) {
var dealCards: CardDistributor = DealInitialCards(
GameTable(
Dealer(),
Players.of(inputProcessor.playerNames()) { player -> inputProcessor.playerAction(player) }
),
)

var dealCards: CardDistributor = DealInitialCards(GameTable(players))
private set

val betBoard: BetBoard = BetBoard.of(players) { player -> inputProcessor.playerBet(player) }

fun run() {
var distributionCount = 0
while (dealCards !is DealEnd && distributionCount < MAX_DISTRIBUTION_COUNT) {
Expand All @@ -40,8 +42,10 @@ class BlackJackGame(
}

private fun endDeal() {
val result = dealCards.deal()
emitResult(result)
val dealEndResult = dealCards.deal() as? DealEndResult
?: throw IllegalArgumentException("배분이 종료되지 않았습니다")
betBoard.closeBetting(dealEndResult)
emitResult(GameEndResult.of(dealEndResult, betBoard))
}

companion object {
Expand Down
28 changes: 28 additions & 0 deletions src/main/kotlin/blackjack/domain/BlackJackJudge.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package blackjack.domain

import blackjack.domain.player.CardPlayer
import blackjack.domain.player.Player
import blackjack.domain.result.game.VictoryStatus

object BlackJackJudge {
private const val BUST_SCORE = 0
private const val BLACK_JACK_CARD_COUNT = 2
private const val BLACK_JACK_SCORE = 21
Copy link
Member

Choose a reason for hiding this comment

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

21이라는 비즈니스 상수값이 BlackJackJudgeHandScore에 존재하고 있어요. 21을 이렇게 분산되어서 관리하면 하나의 클래스가 변경하여야할 때 다른쪽 클래스도 변경하는 것에 대해서 의식하지 못할 수도 있을 것 같은데 한군데서 관리할 수 있도록 하는 방법은 없을까요?

Copy link
Author

@bong6981 bong6981 Dec 31, 2023

Choose a reason for hiding this comment

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

제가 도메인에 대한 지식이 부족해서 발생한 코드 같습니다

  • 21점 이상을 가질 수 없다 / 블랙잭이라면 점수 21점을 얻는다. 이렇게 아예 다르게 인식했고, 그에 따라 각기 다른 클래스에 두게 되었습니다
  • BlackJackJudge 가 HansScore 의 MAX_SCORE 을 참조할 수도 있겠지만 이는 다른 성격이라 생각이 드는데용
    어떻게 생각하시는지 궁금합니다!


fun judgeVictory(player: Player, dealer: Dealer): VictoryStatus =
when {
player.isBust() -> VictoryStatus.LOSS
player.gameScore() > dealer.gameScore() -> VictoryStatus.WIN
player.gameScore() == dealer.gameScore() -> VictoryStatus.PUSH
else -> VictoryStatus.LOSS
}

fun isBlackJack(player: Player): Boolean =
(player isSameCardCount BLACK_JACK_CARD_COUNT) && (player isSameScore BLACK_JACK_SCORE)

private fun CardPlayer.gameScore(): Int =
if (this.isBust()) BUST_SCORE
else this.score.value

private fun CardPlayer.isBust() = score.value > BLACK_JACK_SCORE
}
2 changes: 1 addition & 1 deletion src/main/kotlin/blackjack/domain/GameTable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import blackjack.domain.player.Player
import blackjack.domain.player.Players

data class GameTable(
val dealer: Dealer,
val players: Players,
val dealer: Dealer = Dealer(),
) {
val isLastPlayerTurn: Boolean
get() = players.isLastTurn
Expand Down
24 changes: 24 additions & 0 deletions src/main/kotlin/blackjack/domain/batting/Amount.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package blackjack.domain.batting

import java.math.BigDecimal

data class Amount(
val value: BigDecimal,
) : Comparable<Amount> {

operator fun plus(other: Amount): Amount = Amount(value.plus(other.value))
operator fun times(count: Int): Amount = Amount(value.times(count.toBigDecimal()))

operator fun times(count: BigDecimal): Amount =
Amount(value * count)
override fun compareTo(other: Amount): Int =
this.value.compareTo(other.value)

companion object {
val ZERO = Amount(BigDecimal.ZERO)
fun betAmount(amount: Int): Amount {
Copy link
Member

Choose a reason for hiding this comment

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

  1. Int로 들어온 value를 단순히 BigDecimal로 변환하고 있기 때문에 betAmount 보다는 Amount를 생성한다는 의미가 더 어울리지 않을까요?
  2. 정적팩토리메소드를 사용하고 있다면 생성자를 private하게 해주어도 되지 않을까요?
  3. 혹은 정적팩토리메소드를 사용하지 않고 부생성자에서 처리하는건 어떨까요? 이정도의 타입을 변화하는 로직만 존재한다면 부생성자를 추가해도 충분할 것 같아보여서요.

Copy link
Author

Choose a reason for hiding this comment

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

  • Amount 는 단순 금액의 의미를 나타냅니다. Amount 자체는 0이 될 수 있습니다 ( 플레이어의 수령 금액 등 )
  • betAmount 는 Amount 중 베팅 금액에 대한 의미를 나타내기 때문에 팩토리 메서드로 분리해 검증했습니다
    해서 베팅금액에 대한 정적 팩토리 메서드를 따로 정의했습니다.!

Copy link
Author

Choose a reason for hiding this comment

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

BetAmount(val value: Amount) 로 분리하지 않은 이유는 단순 생성시에서 값 체크만 BetAmount 로서의 기능을 한다고 생각해서 정적 팩토리 메서드로 검증을 하도록 했습니다!

Copy link
Member

Choose a reason for hiding this comment

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

말씀하신대로의 Amount가 0이 될 수 있다면 음수를 검증하는 validation은 Amount에서 필요해보입니다.
(반영하지 않으셔도 됩니다.) Amount가 단순 금액을 의미하고, "배팅 금액"은 금액에서 배팅이라는 비즈니스로직이 들어간다면 오히려 분리하는 것이 어떨까하네요. 개인적으로는 betAmount으로 호출의 의도하여도, 그 파라미터나 필드가 Amount 생성자를 통해서 생성하여 사용하게 된다면을 가정해보면 원하는 객체에 대한 생성 안전성이 떨어져보입니다.

Copy link
Author

Choose a reason for hiding this comment

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

BetAmount 로 이를 분리했습니다!

require(amount > 0) { "베팅 금액은 0보다 커야 합니다" }
return Amount(amount.toBigDecimal())
}
}
}
Loading