-
Notifications
You must be signed in to change notification settings - Fork 82
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
[블랙잭 1단계] 뭉치 미션 제출합니다. #122
base: m6z1
Are you sure you want to change the base?
Conversation
- 카드 모양 추가
- 카드는 모양과 끗수를 가진다.
- 카드의 끗수에 대한 요구 사항 추가
- 응답에 대한 플레이어 행동 반환
- 이름, 카드 리스트 가짐
- 기존) List<Card> - 변경) Cards
- 딜러의 hit 과 stay 로직 제외 구현
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.
안녕하세요 둘리!
고민이 많았던 부분에 대해서 코멘트를 남겨두었습니다.
이번 미션 잘 부탁드립니다 : )
package blackjack.model | ||
|
||
class Cards( | ||
value: List<Card>, |
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.
기존의 cards의 생성자 네이밍은 cards 였습니다.
일급컬렉션을 만들어 외부에서 사용하게 되면 , List<Card>
에 접근하기 위해서는
cards.cards.~~
로 사용을 했어야 했는데, 미관상으로도 그렇고 카드들의 카드들이라는 이름이 어색하다고 느꼈습니다.
이에 value라는 네이밍을 사용했는데, 조금 추상적인가? 라는 생각도 들었습니다.
둘리는 어떻게 생각하시는지 궁금합니다..!!
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.
취향차이라고 생각하는데, 저는 cards.cards
를 더 많이 사용하긴 합니다. 정답은 없어요!
) { | ||
fun isDenominationAce(card: Card): Boolean = card.denomination == Denomination.ACE | ||
|
||
fun combine(): String = denomination.title + shape.koreanName |
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.
combine이란 함수가 String 을 반환하는데, 다시 생각해보니 view에서 결합하여 출력을 하는 게 더 적절할 것이라는 생각이 들었습니다.
A다이아몬드
에서 다이아몬드A
로 출력 요구 사항이 변경될 경우, model이 아닌 view에서 변경을 해야 하지 않나?
하지만, 반대로 생각하면 card에서 메시지를 받아 반환 할 수 있는 것은 아닐까? 라는 생각이 들었습니다...
고민의 끝에 현재 코드는 card 에서 직접 제작해서 제출하고 있습니다.
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.
combine
함수의 로직은 뷰의 관심사가 맞습니다.
그럼 이렇게 생각해보면 어떨까요?
- 이 도메인 로직을 여러 플랫폼에서 사용한다
combine()
함수로 뷰에 출력할 String값을 받는다.- A플랫폼에서는
A다이아몬드
, B플랫폼에서는B다이아몬드
라고 출력해야한다.
뷰의 요구사항이 바뀌면 뷰를 수정하는 것이 자연스럽지 않을까요?
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.
현재 Dealer는 Player를 상속받고 있습니다.
하지만, dealer가 아닌 player들은 딜러
라는 이름을 가질 수 없다고 판단했습니다.
하지만, init
을 통해 이름에 대한 검증을 진행하게 되면, 딜러 또한 이 에러에 막히는 것을 깨달았습니다.
그래서 dealer 가 아닌 player의 경우에는 팩토리 함수를 통해 객체를 생성하며 이름을 검증할 수 있도록 했고,
dealer의 경우에는 player의 생성자를 통해 직접적으로 객체를 생성할 수 있도록 했습니다.
하지만, 생성자는 public 으로 열려있어서 누구든지 Player("이름")을 통해 객체를 생성할 수 있습니다.
dealer 가 아닌 player 는 생성자로 접근해 Player 객체를 만들 수 있으니 그렇다면 팩토리 메서드가 의미가 있는 것일까? 라는 생각이 들었습니다.
어떻게 문제를 해결해야 할 지 방향성을 제시해주시면 감사하겠습니다 ㅠㅠ!!
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.
생성자가 public으로 열려있더라도, 생성자 외에 다른 의도를 주고 싶다면 팩토리 함수를 사용해도 이상하지 않다고 생각해요.
하지만 요구사항에 대한 검증은 init
에 두는 것이 자연스럽다고 생각합니다. 말씀하신대로 누구든지 Player("이름")을 통해 객체를 생성할 수
있기 때문이에요.
플레이어와 딜러에게 다른 요구사항을 검증하고 싶다면, 딜러가 플레이어를 상속하는 것이 아니라
딜러-플레이어를 공통화할 수 있는 새로운 상위 클래스를 생성하고, 딜러와 플레이어가 그 클래스를 상속받는 형태는 어떨까요?
추가로 플레이어 이름이 겹치면 안된다는 요구사항은 없는데요. 🤔 다른 이야기일 수 있겠지만 동명이인이라면요..?🥹
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.
안녕하세요, 뭉치! 이번 미션을 함께 하게 된 둘리입니다. 반가워요! 😄
객체를 많이 나눠주셨어요! 여기서 좀 더 어떤 객체가 어떤 정보를 가지고 있어야 할지, 어떤 행위를 해주어야할지 고민해보면 좋겠어요.
질문에 대한 답과 함께 고민해볼만한 내용들을 코멘트로 달아드렸습니다.
더 궁금한 점이 있으시면 코멘트 달아주시거나 DM 주세요! 💪
import blackjack.view.InputView | ||
import blackjack.view.OutputView | ||
|
||
class BlackjackController( |
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.
딜러가 ~하면, 플레이어가 ~한 경우의 분기가 굉장히 많다보니
혹시 이런 경우의 테스트가 어떤 것이 있을까요?
보통은 단위 테스트면 충분하다고 생각하고, 전제조건을 초기값으로 제공해서 테스트하는 함수들이 지금도 보여서요!
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.
별개로 지금도 컨트롤러가 하고있는게 많아보이긴하는데... 우선 다른 것에 집중해서 수정해보고, 그 이후에 컨트롤러를 살펴보죠!
) { | ||
fun isDenominationAce(card: Card): Boolean = card.denomination == Denomination.ACE | ||
|
||
fun combine(): String = denomination.title + shape.koreanName |
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.
combine
함수의 로직은 뷰의 관심사가 맞습니다.
그럼 이렇게 생각해보면 어떨까요?
- 이 도메인 로직을 여러 플랫폼에서 사용한다
combine()
함수로 뷰에 출력할 String값을 받는다.- A플랫폼에서는
A다이아몬드
, B플랫폼에서는B다이아몬드
라고 출력해야한다.
뷰의 요구사항이 바뀌면 뷰를 수정하는 것이 자연스럽지 않을까요?
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.
생성자가 public으로 열려있더라도, 생성자 외에 다른 의도를 주고 싶다면 팩토리 함수를 사용해도 이상하지 않다고 생각해요.
하지만 요구사항에 대한 검증은 init
에 두는 것이 자연스럽다고 생각합니다. 말씀하신대로 누구든지 Player("이름")을 통해 객체를 생성할 수
있기 때문이에요.
플레이어와 딜러에게 다른 요구사항을 검증하고 싶다면, 딜러가 플레이어를 상속하는 것이 아니라
딜러-플레이어를 공통화할 수 있는 새로운 상위 클래스를 생성하고, 딜러와 플레이어가 그 클래스를 상속받는 형태는 어떨까요?
추가로 플레이어 이름이 겹치면 안된다는 요구사항은 없는데요. 🤔 다른 이야기일 수 있겠지만 동명이인이라면요..?🥹
open fun appendCard(card: Card) { | ||
cards.add(card) | ||
} |
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.
Controller에서 deck에서 카드를 뽑아서 주지말고, player가 직접 deck에서 카드를 뽑는다면 어떨까요?
fun calculateScore(): Int { | ||
val aceCount: Int = value.count { card -> card.isDenominationAce(card) } | ||
val score: Int = value.sumOf { card -> card.denomination.number } | ||
return when (aceCount) { | ||
1 -> if (score < 11) score + 11 else score + 1 | ||
2 -> if (score < 10) score + 11 + 1 else score + aceCount * 1 | ||
3 -> if (score < 9) score + 11 + 1 + 1 else score + aceCount * 1 | ||
4 -> if (score < 8) score + 11 + 1 + 1 + 1 else score + aceCount * 1 | ||
else -> score | ||
} | ||
} |
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.
이 계산 로직이 이렇게 길지 않아도 해결할 수 있을 것 같아요. 🤔
private val _value: MutableList<Card> = value.toMutableList() | ||
val value: List<Card> get() = _value.map { card -> card.copy() } | ||
|
||
var size: Int = this.value.size |
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.
이 변수는 테스트를 위함일까요? 🤔
open val name: String, | ||
open val cards: Cards = Cards(emptyList()), | ||
) { | ||
lateinit var result: GameResult |
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.
사실 저는 이 result
가 왜있는지 잘 모르겠어요!
result
는 결과 계산이 필요할 때만 계산해서 보여주면 될 것 같은데, 게임 진행 내내 갱신을 하고 있더라구요.
진행 로직 정리하면서 이 result
가 꼭 필요한지 고민해보면 좋겠어요.
"Y" -> HIT | ||
"N" -> STAY |
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.
어차피 하드코딩된 "Y"와 "N"에 따라 결과값을 리턴할거라면 enum 내의 answer
는 정의하지 않아도 괜찮아보여요.
파라미터 answer
가 지금 String
인데, 진행 여부를 Boolean
으로 받아도 충분하지 않을까요?
fun isBlackjack(firstTurn: Boolean): Boolean = CardsStatus.from(calculateScore(), firstTurn) == CardsStatus.BLACKJACK | ||
|
||
fun isBust(): Boolean = CardsStatus.from(calculateScore()) == CardsStatus.BUST |
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.
상태를 활용하겠다면, Cards
가 상태를 가지고, 카드가 추가될 때마다 상태를 업데이트하는 것이 자연스럽지 않을까요?
private val DENOMINATIONS: List<Denomination> = Denomination.entries | ||
private val SHAPES: List<CardShape> = CardShape.entries | ||
|
||
val CARDS: List<Card> = | ||
DENOMINATIONS | ||
.flatMap { denomination -> | ||
SHAPES.map { shape -> | ||
Card(shape, denomination) | ||
} | ||
}.shuffled() |
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.
Kotlin에서 이렇게 전부 대문자로 이름을 짓는 경우는 어떤 경우일까요?
추가로 전체적인 블랙잭 로직을 확인해보면 좋겠어요.
위 경우는 딜러가 이겨야합니다. 딜러가 Bust가 아니면서 21 숫자에 가깝기 때문이에요. 제가 아는 블랙잭 규칙으로는 플레이어들이 모두 카드뽑기가 끝나면 딜러가 카드를 더 뽑는 것으로 알고 있는데요. 지금은 한 명의 플레이어가 카드를 뽑으면 딜러가 뽑고 있어요. 전체적으로 로직을 한번 체크해보시면 좋을 것 같아요! 객체 간의 역할과 책임을 고민하다보면 컨트롤러 로직도 좀 더 단순화되지 않을까 싶네용 💪 |
안녕하세요 둘리!
모바일 안드로이드 크루 뭉치입니다 ㅎㅎ
이렇게 만나뵙게 되어 영광입니다 😄🍀
블랙잭 미션을 돌아가게하자! 라는 부분에 초점을 두어, 코드가 질서정연하지 않고 가독성이 떨어지는 것 같습니다..
따끔하게 피드백 주시면 감사하겠습니다!!
이번 블랙잭 미션 잘 부탁드려요!!🙇♀️
셀프 체크리스트
README.md
에 기능 목록을 정리하고 명확히 기술하였는가?어떤 부분에 집중하여 리뷰해야 할까요?
1. 모든 객체를 설계하고 컨트롤러 및 뷰를 생성하다보니 생각보다 컨트롤러가 하는 역할이 많다? 라는 생각이 들었습니다.
저는 기존에 controller에서 객체를 생성해도 되며, 서비스의 흐름을 가지는 것이 컨트롤러의 역할이라고 생각해 블랙잭 게임의 흐름을 모두 컨트롤러에서 구현했습니다. 하지만, 이번 블랙잭 미션에서 느낀 문제점이 있었습니다.
단위 테스트만 진행하다보니 여러 가지의 분기에 대해서 테스트를 할 수 없다는 문제점을 깨달았습니다. 특히나 블랙잭 미션의 경우에는 딜러가 ~하면, 플레이어가 ~한 경우의 분기가 굉장히 많다보니 실제로 그 플로우를 테스트하기까지 application을 계속 돌려야 하는 문제가 있었습니다.
그렇다면 블랙잭 게임을 주관하는 새로운 도메인 객체를 만들어야 할까? 라는 생각도 들었는데 이것에 대해서는 어찌 생각하시는지 궁금합니다!
이번 피드백에서 만들어보라고 하시면 반영하고 둘의 장단점을 느껴보도록 하겠습니다!! ㅠㅠ
다른 내용은 코멘트로 달겠습니다!