[2단계 - 블랙잭 베팅] 모다(이채영) 미션 제출합니다.#909
Conversation
Chaeyoung714
left a comment
There was a problem hiding this comment.
집중 구현한 부분과 궁금한 점에 대해 아래 코멘트로 달아두었으니, 확인 부탁드립니다!
| ); | ||
| } | ||
|
|
||
| @Override |
There was a problem hiding this comment.
스스로 일하게 하기 vs 딜러가 책임지기
splitInitialDeck(), receiveBet() ~ findBetByBetter() 메서드가 이번 구현에서 가장 고민이 되는 사항이었습니다.
이들은 모든 참가자에게 카드를 2장씩 나눠주는 역할과, 플레이어에게서 배팅금액을 받고 이를 딜러가 갖거나 돌려주는 역할을 구현한 메서드인데요,
이것들은 요구사항에선 모두 딜러의 역할로 소개되었습니다.
하지만 이것들은 현실세계에서의 역할이지, 객체세계에서는 의인화를 통해 Deck, Bet과 Bet을 담은 Bets가 스스로 일하게 만드는 게 더 자연스러울 것 같았습니다.
즉 딜러 대신 BetManager와 같은 객체를 만들어서 Bets를 관리하는 게 더 응집도있는 설계가 아닐까 싶었어요.
그러나 이번에는 요구사항에 맞게 일단 딜러의 객체에 응집되도록 만들었습니다.
그리고 딜러에 응집되면 조금 더 '딜러 위주의 사고'를 할 수 있어서, 구현이 쉬웠던 이점도 있긴 했습니다.
(특히 딜러의 수익은 모든 Bet을 조회하면서 계산해야 하기 때문에, 딜러가 갖고있는게 구현의 간편함에 있어선 더 좋았습니다.)
그래도 계속 딜러에게 너무 많은 책임이 있는 건 아닌가 하는 생각이 들어 GameProcessable 인터페이스를 통해 딜러의 역할이 좀더 눈에 들어오게 만들고, 책임을 최대한 분리해 코드의 복잡성도 줄였습니다.
이에 대해 라라라면 어떻게 생각하시는지 궁금합니다.
지금처럼 딜러에게 주어진 일은 딜러가 수행하도록 해야 할까요?
아니면 요구사항을 다르게 해석하더라도 덱이나 베팅 객체가 스스로 베팅을 관리하도록 만들어야 할까요?
There was a problem hiding this comment.
1. 딜러의 역할
현재 설계에서는 딜러가 게임 진행의 중심 역할을 하며, 두가지 역할을 모두 담당하고 있네요.
- 카드 분배 (splitInitialDeck)
- 배팅 관리 (receiveBet, updateBetOwnerFrom, updateBetAmountOf, findBetByBetter)
이렇게 설계하면, 딜러의 역할이 직관적이고 Bets를 소유하면서 직접 관리하니, 딜러의 수익을 계산하는 것도 간단하긴 하지만,
객체지향적으로 봤을 때 딜러가 너무 많은 책임을 가지고 있는 것 같아요.
베팅과 카드 분배는 딜러가 직접 하지 않아도 되지 않을까요?
2. BetManager 도입
딜러는 단순히 '게임을 진행하는 사람'이고, 베팅 자체를 관리하는 역할은 별도의 객체가 담당하면 더 객체지향적인 설계가 될 수 있을 것 같아요.
BetManager가 Bets를 관리하면서 승패에 따라 베팅 소유자를 변경하고, 베팅 금액을 변경하거나 딜러 수익을 계산하게 되면
딜러의 책임을 줄이고 BetManager가 베팅의 상태 변경을 관리하니 응집도가 올라갈 것으로 예상되네요.
3. Deck이 스스로 일하기
현재 splitInitialDeck은 딜러가 Deck을 사용해 각 참가자에게 카드를 분배하고 있는데요.
딜러가 직접 분배하는 게 직관적이긴 하지만, Deck이 직접 플레이어들에게 카드를 줄 수도 있을 것 같아요.
public class Deck {
public void dealTo(Gameable gamer, int cardCount) {
IntStream.range(0, cardCount).forEach(
i -> gamer.receiveCard(pick())
);
}
}이렇게 만들면 딜러가 deck.deal(player, 2) 같은 방식으로 사용할 수 있겠네요!
이렇게 하면 딜러는 단순히 "이 사람에게 카드 주세요"라고 요청하는 역할만 하고 Deck이 스스로 카드를 나눠주는 역할을 하게 되겠네요 🙂
요구사항을 명확하게 반영해야 하는 경우 지금처럼 딜러가 모든 배팅과 카드 분배를 관리하는 설계가 직관적일 수 있겠지만
객체지향적으로 책임을 분리해야 하는 경우 BetManager를 도입하고, Deck이 직접 카드를 나눠주는 방식이 더 응집력 있는 설계가 되지 않을까 싶어요 🙂
There was a problem hiding this comment.
아하 이해되었습니다!!
객체지향 세계에 맞춰서 각자 의인화를 시키는 게 훨씬 낫겠군요 ㅎㅎ 훨씬 더 책임이 잘 분리되었습니다.
| @@ -0,0 +1,4 @@ | |||
| package model.participant.role; | |||
There was a problem hiding this comment.
인터페이스 사용의 적절성
이번엔 보시다시피 4개의 인터페이스를 썼는데요, 원래는 인터페이스를 쓰기엔 아직 지식이 모자라고 쓰는 이유가 완전히 납득되지 않았다고 생각해 쓰지 않으려고 했는데,
만들다보니 이에 대한 필요성이 많이 느껴져서 추가하게 되었습니다.
각각의 인터페이스가 적절한 이유를 가지고 있는지 라라의 의견이 궁금합니다.
1. BetOwnable을 만든 이유
BetOwnable은 Dealer, Player 모두가 구현중이고, '베팅금액을 (최종적으로) 가질 수 있다'라는 역할을 제공합니다.
이는 Bet 객체에서 베팅금액을 가지는 owner와 베팅을 건 better를 나누어 관리하는데,
owner 필드에 들어갈 수 있는 타입(Dealer, Player 모두)을 다형적으로 허용하고 싶어서 생성했습니다.
대신 Dealer와 Player가 '베팅금액을 가질 수 있다'는 역할 아래 공통적으로 가질만한 메서드가 없어서, 메서드가 존재하지 않습니다.
이런 인터페이스도 다형성을 적절하게 활용 중이라면 괜찮은지도 궁금합니다.
2. Bettable을 만든 이유
이역시 1번과 동일하게 Bet 객체의 better 필드에 다형성을 허용하기 위해서인데, 현재는 Player만 허용되어 있습니다.
이를 통해 Bet 필드에서 '베팅을 하는 사람은 Player만 가능하고, 베팅을 최종적으로 갖는 사람은 Player와 Dealer 모두 가능하다'라는 제한을 걸 수 있었습니다.
또한 Bet과 Bets 객체에서 구현체인 Dealer와 Player를 아예 모르고 인터페이스만으로 메스드를 구현해 유연한 적용이 가능했습니다.
3. Gameable을 만든 이유
이는 Dealer와 Player가 모두 구현 중이고, '게임에 참여할 수 있다'라는 역할을 제공합니다.
여기엔 Dealer와 Player가 모두 가지고 있는 게임참여와 관련된 메세지를 모두 정의 중입니다.
그래서 게임 참여자라면 반드시 가져야 하는 메세지를 강제할 수 있습니다.
또한 딜러의 역할인 '모든 참여자에게 최초 카드를 나눠준다'를 구현한 dealer.splitInitialDeck()에서 나눠주는 대상을 Gameable로 추상화할 수 있습니다.
이를 통해 하나의 메서드에서 dealer, player 모두 카드를 분배해줄 수 있었습니다.
4. GameProcessable을 만든 이유
이는 Dealer만 구현 중이고, '게임을 진행 가능하다'는 역할을 부여한 인터페이스입니다.
따라서 Dealer에게 이러한 역할을 부여했음을 가독성있게 보여줄 수 있었습니다.
대신 여러 타입이 구현중이진 않아서 다형성은 활용하지 않았는데, Dealer에 역할을 보기쉽게 다중으로 부여한다는 점에서 장점을 느껴서 사용했습니다.
또한 초반엔 딜러가 배팅을 관리하는 책임을 어디까지 구현할지 잘 몰라서 헤맸는데, 인터페이스를 도입하고 리팩토링해보니 딜러의 역할이 좀더 추상화가 되었습니다.
따라서 딜러에게 과도한 책임을 주지 않고 딱 '딜러는 어떠한 상황인지는 관심없고 배팅 소유자가 바뀌면 이를 바꿔주기만 한다', '딜러는 어떠한 상황인지는 관심없고 배팅금액을 올려달라 하면 이를 올려주기만 한다'라는 역할만 수행할 수 있었습니다.
어느 상황에서 딜러가 이런 동작을 해야 하는지는 외부에서 책임을 지고 있습니다.
There was a problem hiding this comment.
오.. 설계하신 구조와 의견이 굉장히 인상적이네요. 💯👏🏻👍🏻
인터페이스를 활용한 역할 기반 설계가 훌륭하네요!
객체 간의 결합도를 낮추고, 단일 책임 원칙을 잘 따르도록 변경되어 보이네요.
다른 형태의 참여자가 생기더라도 다형성을 적극적으로 활용할 수 있는 확장성있는 구조네요.
너무 잘 고민하고 적용해주신 것 같아요. 😎
There was a problem hiding this comment.
우와 좋은 코멘트 감사합니다 ㅎㅎ
근데 BetOwnable같은 경우 인터페이스에서 강제하는 메세지가 아예 없고,
GameProcessable이나 Bettable 같은 경우에는 구현체가 딱 한개만 존재합니다.
그래서 사실 인터페이스의 존재가 꼭 필요하냐는 피드백을 받진 않을까 생각도 했는데요,
결론적으로는
- 인터페이스 안에 메세지가 없는 것
- 인터페이스에 대한 구현체가 하나만 존재하는 것 (즉 유연하게 구현체를 갈아끼운다는 이점이 사라지는 것)
에 대해서는 어떻게 생각하시는지도 궁금합니다.
There was a problem hiding this comment.
1. 인터페이스 안에 메시지가 없는 것
단순한 타입 구분용 인터페이스는 필요할까? 인터페이스는 보통 객체의 행동(메서드)을 강제하는 역할을 합니다.
그런데 BetOwnable처럼 메서드 없이 이 객체가 특정 그룹에 속한다는 정보만 제공하는 경우, 인터페이스로 정의하는 것이 정말 필요한지 고민해볼 필요가 있을 것 같아요!
BetOwnable이 타입을 명확히 하기 위해 사용되었듯이, java.io.Serializable 도 직렬화가 가능한 객체임을 나타내지만, 별도 메서드는 없는 예시중 하나에요.
2. 인터페이스에 대한 구현체가 하나만 존재하는 것
보통 인터페이스를 사용하는 이유는 다형성 지원, 결합도 낮추기, 역할의 명확화같은 이유가 있는데요.
만약 인터페이스의 구현체가 하나뿐이라면, 구현체를 쉽게 교체 가능한 다형성의 이점이 사라지고, 불필요한 추상화가 될 수도 있죠.
인터페이스가 향후 다른 구현체를 추가할 가능성이 있다면 유지하고, 구현체가 하나뿐인데 향후에도 바뀔 가능성이 거의 없다면, 인터페이스를 제거하고 그냥 클래스로 변환하는 방법도 있을 것 같아요!
사실 저는 미션을 통해 불필요한 추상화 등 다양한 시도를 해보면서 직접 장/단점을 느껴보는 경험이 좋다고 생각해요!
그래서 모다의 멋진 고민과 좋은 시도였다고 생각합니다. 😎👏🏻👍🏻
| import model.deck.Card; | ||
| import model.deck.CardRank; | ||
|
|
||
| public final class SoftHand extends ParticipantHand { |
There was a problem hiding this comment.
소프트핸드, 하드핸드 추가
블랙잭에서 소프트핸드는 에이스카드가 포함된 핸드, 하드핸드는 포함되지 않은 핸드를 말한다고 합니다.
각각이 모두 핸드이지만 소프트인지 하드인지에 따라 최종결과를 계산하는 로직이 달라지므로, 상속이 매우 적절하다고 생각해 상속을 활용한 구현을 했습니다.
그 이유는 다음과 같습니다.
- SoftHand is Hand, HardHand is Hand이다.
- 외부에선 이 핸드가 소프트인지 하드인지 전혀 알필요가 없으므로 다형성을 활용할 수 있다. (즉 구현체에 따라서 크게 달라지는 메세지가 없다)
이를 추가하자 ParticipantHand에서 과도하게 분기문을 사용했던 로직이 깔끔하게 나뉘고,
객체분리를 하니 엄청나게 비효율적이고 복잡하게 점수계산을 구현했다는 것도 알 수 있었습니다.
이와 같은 상속의 사용은 적절하게 사용한 게 맞을까요?
저번 리뷰에서 라라와 함께 얘기했던 걸 바탕으로 상속을 옳게 활용했는지 체크하며 구현했는데, 이에 대한 라라의 의견이 궁금합니다.
이에 대해 이 코멘트까지 함께 확인 후 답변해주시면 감사하겠습니다!!
There was a problem hiding this comment.
오 모다덕분에 몰랐던 소프트핸드, 하드핸드도 알게되었네요. ㅎㅎ
일단 상속과 다형성을 잘 활용해주셨네요.👍🏻
SoftHand와 HardHand는 모두 ParticipantHand로 추상화되어서 각 클래스의 세부 구현에 따른 차이를 잘 나누고 있네요.
cloneToSoftHand() 메서드를 활용해 HardHand가 SoftHand로 변환되는 과정을 처리한 것도 좋은 접근인 것 같아요!
다형성을 활용하여 각 핸드 타입이 다른 방식으로 최종 점수를 계산할 수 있도록 구현한 부분이 직관적이고,
핸드가 soft인지 hard인지 구분할 필요가 없이 외부에서 추상화된 ParticipantHand를 사용해서 일관된 인터페이스를 유지할 수 있네요.
상속을 통한 다형성을 잘 활용하고, OCP를 만족하는 설계를 해주셨네요. 😀💯👏🏻
There was a problem hiding this comment.
우왓..! 좋은 코멘트 감사합니다 ..!! 🙇♂️🚀
| if (card.isSoftCard()) { | ||
| this.participantHand = this.participantHand.cloneToSoftHand(); | ||
| } | ||
| participantHand.add(card); |
There was a problem hiding this comment.
소프트핸드, 하드핸드 사용에서 아쉬운 점
아쉬운 점은 로직 상 participantHand의 구현체가 동적으로 변해야 한다는 점입니다.
모든 hand는 시작 시점에선 hardhand입니다. 카드가 아예 없으므로 ace 카드도 없기 때문입니다.
이후 card를 한장씩 add할때 hand의 종류가 바뀔 수 있는데,
최초로 ace를 뽑은 경우 participantHand 필드는 hardhand -> softhand 로 변해야 합니다.
따라서 participanthand는 final필드가 될 수 없어서 불변성이 깨집니다.
그래서 로직이 맞는지 고민이 많았는데, 결국 final을 빼는 것을 선택했습니다.
- hardhand와 softhand를 나누었을 때 이점이 크고,
- 필드를 getter로 꺼내올 수 없게 잘 캡슐화되어있는 상태여서 안전하다고 생각했기 때문입니다.
이러한 트레이드오프에 대한 저의 결정에 대해 라라의 생각은 어떤지 궁금합니다.
또 제가 잘 모르는 지식으로 이를 더 잘 구현할 수 있는 방법... 은 없는지..? 도 궁금합니다.
There was a problem hiding this comment.
불변객체의 장점을 잃게되긴 하지만, 실제 시나리오상 ace카드를 받는 시점부터 소프트핸드로 변경되기 때문에
저는 final을 제외한 선택이 적절하다고 생각해요. 🙂
저도 불변성을 보장하려고 시도해봤는데 오히려 복잡해지고 방법이 없긴 하네요..😅
|
|
||
| @Override | ||
| public boolean isBurst() { | ||
| return participantHand.checkBurst(); |
There was a problem hiding this comment.
지난 step1에서 라라의 리뷰를 반영해 조합으로 중복을 제거했습니다!
덕분에 중복은 제거되면서도 dealer와 player를 억지로 묶어서 생각하던 것에서 자유로워져서, 로직이 훨씬 유연해진 것 같습니다!
또한 다른 코멘트에서 단 것처럼, 완전히 하위 타입이 아니라 일부만 역할이 겹친다면 인터페이스로 추상화할 수 있다는 점도 새롭게 배웠습니다.
좋은 리뷰 덕분에 이번주는 상속이나 결합도, 응집도, 객체 등에 대해 제가 잘 몰랐다는 것도 느끼고 다시 공부할 수 있었습니다. 감사합니다 😄
sure-why-not
left a comment
There was a problem hiding this comment.
모다 안녕하세요!😀
인터페이스를 활용한 역할 기반 설계가 인상적이네요.💯👍
질문에 대한 답변과 몇가지 코멘트 남겨두었으니 확인 부탁드려요~!
| import model.deck.Card; | ||
| import model.deck.CardRank; | ||
|
|
||
| public final class SoftHand extends ParticipantHand { |
There was a problem hiding this comment.
오 모다덕분에 몰랐던 소프트핸드, 하드핸드도 알게되었네요. ㅎㅎ
일단 상속과 다형성을 잘 활용해주셨네요.👍🏻
SoftHand와 HardHand는 모두 ParticipantHand로 추상화되어서 각 클래스의 세부 구현에 따른 차이를 잘 나누고 있네요.
cloneToSoftHand() 메서드를 활용해 HardHand가 SoftHand로 변환되는 과정을 처리한 것도 좋은 접근인 것 같아요!
다형성을 활용하여 각 핸드 타입이 다른 방식으로 최종 점수를 계산할 수 있도록 구현한 부분이 직관적이고,
핸드가 soft인지 hard인지 구분할 필요가 없이 외부에서 추상화된 ParticipantHand를 사용해서 일관된 인터페이스를 유지할 수 있네요.
상속을 통한 다형성을 잘 활용하고, OCP를 만족하는 설계를 해주셨네요. 😀💯👏🏻
| ); | ||
| } | ||
|
|
||
| @Override |
There was a problem hiding this comment.
1. 딜러의 역할
현재 설계에서는 딜러가 게임 진행의 중심 역할을 하며, 두가지 역할을 모두 담당하고 있네요.
- 카드 분배 (splitInitialDeck)
- 배팅 관리 (receiveBet, updateBetOwnerFrom, updateBetAmountOf, findBetByBetter)
이렇게 설계하면, 딜러의 역할이 직관적이고 Bets를 소유하면서 직접 관리하니, 딜러의 수익을 계산하는 것도 간단하긴 하지만,
객체지향적으로 봤을 때 딜러가 너무 많은 책임을 가지고 있는 것 같아요.
베팅과 카드 분배는 딜러가 직접 하지 않아도 되지 않을까요?
2. BetManager 도입
딜러는 단순히 '게임을 진행하는 사람'이고, 베팅 자체를 관리하는 역할은 별도의 객체가 담당하면 더 객체지향적인 설계가 될 수 있을 것 같아요.
BetManager가 Bets를 관리하면서 승패에 따라 베팅 소유자를 변경하고, 베팅 금액을 변경하거나 딜러 수익을 계산하게 되면
딜러의 책임을 줄이고 BetManager가 베팅의 상태 변경을 관리하니 응집도가 올라갈 것으로 예상되네요.
3. Deck이 스스로 일하기
현재 splitInitialDeck은 딜러가 Deck을 사용해 각 참가자에게 카드를 분배하고 있는데요.
딜러가 직접 분배하는 게 직관적이긴 하지만, Deck이 직접 플레이어들에게 카드를 줄 수도 있을 것 같아요.
public class Deck {
public void dealTo(Gameable gamer, int cardCount) {
IntStream.range(0, cardCount).forEach(
i -> gamer.receiveCard(pick())
);
}
}이렇게 만들면 딜러가 deck.deal(player, 2) 같은 방식으로 사용할 수 있겠네요!
이렇게 하면 딜러는 단순히 "이 사람에게 카드 주세요"라고 요청하는 역할만 하고 Deck이 스스로 카드를 나눠주는 역할을 하게 되겠네요 🙂
요구사항을 명확하게 반영해야 하는 경우 지금처럼 딜러가 모든 배팅과 카드 분배를 관리하는 설계가 직관적일 수 있겠지만
객체지향적으로 책임을 분리해야 하는 경우 BetManager를 도입하고, Deck이 직접 카드를 나눠주는 방식이 더 응집력 있는 설계가 되지 않을까 싶어요 🙂
| if (card.isSoftCard()) { | ||
| this.participantHand = this.participantHand.cloneToSoftHand(); | ||
| } | ||
| participantHand.add(card); |
There was a problem hiding this comment.
불변객체의 장점을 잃게되긴 하지만, 실제 시나리오상 ace카드를 받는 시점부터 소프트핸드로 변경되기 때문에
저는 final을 제외한 선택이 적절하다고 생각해요. 🙂
저도 불변성을 보장하려고 시도해봤는데 오히려 복잡해지고 방법이 없긴 하네요..😅
| ### 딜러 | ||
| - [x] 카드의 합이 16이하면 반드시 카드를 뽑습니다. | ||
| - [x] 카드의 합이 17이상이면 절대로 카드를 뽑을 수 없습니다. | ||
| - [x] 플레이어들이 카드를 뽑게 한다. |
| - [x] 중복 이름 | ||
| - [x] 중복 이름 | ||
| - [x] y,n 선택 예외 | ||
| - [ ] 한 참가자가 가진 카드 간에 중복이 있는 경우 No newline at end of file |
There was a problem hiding this comment.
엇 이런.. 인텔리제이에서 테스트 자동추가를 하면 자꾸 개행이 안되고 클래스가 추가되네요 😅 반영했습니다!
| return this.cards.size() == 2 | ||
| && calculateFinalScore() == 21; |
| protected int calculateDefaultScore() { | ||
| return cards.stream() | ||
| .mapToInt(Card::getCardRankDefaultValue) | ||
| .sum(); | ||
| } |
There was a problem hiding this comment.
ParticipantHand 가 카드 점수를 계산하는 책임을 갖는게 맞을까요? 🤔
Cards 일급컬렉션을 추가하여 관리하도록 하는건 어떠신가요?
There was a problem hiding this comment.
오..! 저도 ParticipantHand가 점수 계산 책임까지 있어야 할지 고민했는데, 카드를 갖고있는 객체가 카드로부터 점수를 계산할 책임에 가장 '가까운' 것 같아서 이렇게 만들었었습니다.
저는 해결을 한다면 카드 점수에 관련된 로직을 아예 Score라는 객체를 만들까 생각했는데,
Participant도 List만을 갖고있는 객체인데 한번 더 Cards 일급컬렉션 추가를 제시해주신 이유가 궁금해요 🧐
There was a problem hiding this comment.
다른 객체에 책임을 부여해야할 것 같은데, 카드들을 관리하는 Cards를 통해 계산해도 좋을 것 같다고 생각했어요. ㅎㅎ
Score 객체를 추가하는 것도 좋습니다. 🙂
| //then | ||
| assertThat(score).isEqualTo(21); | ||
| } | ||
| } No newline at end of file |
| assertThat(initialDealResult.findWinnerPlayers()).containsExactly(player1); | ||
| assertThat(initialDealResult.findLoserPlayers()).containsExactly(player2); | ||
| } | ||
| } No newline at end of file |
| @@ -0,0 +1,4 @@ | |||
| package model.participant.role; | |||
There was a problem hiding this comment.
오.. 설계하신 구조와 의견이 굉장히 인상적이네요. 💯👏🏻👍🏻
인터페이스를 활용한 역할 기반 설계가 훌륭하네요!
객체 간의 결합도를 낮추고, 단일 책임 원칙을 잘 따르도록 변경되어 보이네요.
다른 형태의 참여자가 생기더라도 다형성을 적극적으로 활용할 수 있는 확장성있는 구조네요.
너무 잘 고민하고 적용해주신 것 같아요. 😎
Chaeyoung714
left a comment
There was a problem hiding this comment.
안녕하세요 라라!
블랙잭 2단계 리팩토링 완료했습니다.
크게 진행한 부분은
- 점수값 로직을 Score 객체로 분리
- Dealer의 역할을 Deck, Crucier로 분리
- 기타 컨벤션 및 실수 처리
였습니다.
확인 부탁드립니다. 감사합니다!!
| - [x] 중복 이름 | ||
| - [x] 중복 이름 | ||
| - [x] y,n 선택 예외 | ||
| - [ ] 한 참가자가 가진 카드 간에 중복이 있는 경우 No newline at end of file |
There was a problem hiding this comment.
엇 이런.. 인텔리제이에서 테스트 자동추가를 하면 자꾸 개행이 안되고 클래스가 추가되네요 😅 반영했습니다!
| OutputView.printInitialDeal(players, dealer); | ||
| } | ||
|
|
||
| private boolean continueGame(final Players players, final Dealer dealer) { |
There was a problem hiding this comment.
앗 첫 리팩토링에서 final을 붙이고 그뒤로 추가된 메서드에 있어서는 final 추가를 못해줬습니다...ㅎㅎ
final이 보장되는 매개변수는 모두 final화 하는 것으로 수정했습니다!
| for (Player player : players.getPlayers()) { | ||
| if (winningResults.isLose(player)) { | ||
| dealer.updateBetOwnerFrom(player); | ||
| } | ||
| if (winningResults.isBlackjackWin(player)) { | ||
| dealer.updateBetAmountOf(player); | ||
| } | ||
| } |
| public Bet increase(double rate) { | ||
| int increaseAmount = (int) (money * rate); | ||
| return new Bet(increaseAmount, better); | ||
| } |
There was a problem hiding this comment.
아하..!! 값에 대한 밸리데이션을 잊었네요..!
Money 와 Rate를 추가해서 각각 값에 대한 검증을 캡슐화했습니다.
| public void updateBetAmount(Bettable better) { | ||
| Bet bet = findByBetter(better); | ||
| this.bets.remove(bet); | ||
| this.bets.add(bet.increase(1.5)); | ||
| } |
There was a problem hiding this comment.
원래 딜러는 블랙잭과 같은 상황을 전혀 모르고 그저 '배팅금액을 증가하라'라는 메세지만 처리하도록 만들고 싶었는데, 그러기엔 1.5배를 올리는 역할을 하기 때문에 역할이 너무 구체적이네요..!
아쉽네요ㅜㅜ 이부분은 메서드명을 구체적으로 만들었습니다!
| return this.cards.size() == 2 | ||
| && calculateFinalScore() == 21; |
| protected int calculateDefaultScore() { | ||
| return cards.stream() | ||
| .mapToInt(Card::getCardRankDefaultValue) | ||
| .sum(); | ||
| } |
There was a problem hiding this comment.
오..! 저도 ParticipantHand가 점수 계산 책임까지 있어야 할지 고민했는데, 카드를 갖고있는 객체가 카드로부터 점수를 계산할 책임에 가장 '가까운' 것 같아서 이렇게 만들었었습니다.
저는 해결을 한다면 카드 점수에 관련된 로직을 아예 Score라는 객체를 만들까 생각했는데,
Participant도 List만을 갖고있는 객체인데 한번 더 Cards 일급컬렉션 추가를 제시해주신 이유가 궁금해요 🧐
| import model.deck.Card; | ||
| import model.deck.CardRank; | ||
|
|
||
| public final class SoftHand extends ParticipantHand { |
There was a problem hiding this comment.
우왓..! 좋은 코멘트 감사합니다 ..!! 🙇♂️🚀
| ); | ||
| } | ||
|
|
||
| @Override |
There was a problem hiding this comment.
아하 이해되었습니다!!
객체지향 세계에 맞춰서 각자 의인화를 시키는 게 훨씬 낫겠군요 ㅎㅎ 훨씬 더 책임이 잘 분리되었습니다.
| @@ -0,0 +1,4 @@ | |||
| package model.participant.role; | |||
There was a problem hiding this comment.
우와 좋은 코멘트 감사합니다 ㅎㅎ
근데 BetOwnable같은 경우 인터페이스에서 강제하는 메세지가 아예 없고,
GameProcessable이나 Bettable 같은 경우에는 구현체가 딱 한개만 존재합니다.
그래서 사실 인터페이스의 존재가 꼭 필요하냐는 피드백을 받진 않을까 생각도 했는데요,
결론적으로는
- 인터페이스 안에 메세지가 없는 것
- 인터페이스에 대한 구현체가 하나만 존재하는 것 (즉 유연하게 구현체를 갈아끼운다는 이점이 사라지는 것)
에 대해서는 어떻게 생각하시는지도 궁금합니다.
sure-why-not
left a comment
There was a problem hiding this comment.
모다 안녕하세요! 😀
추상화를 통해 다양한 시도를 해주신 점이 인상깊었습니다. 👍🏻
다음 미션에 집중할 수 있도록 이만 머지하겠습니다.
블랙잭 미션 하느라 고생 많으셨고, 남은 우테코 기간동안 많은 것을 얻어가시길 바랍니다 😀
그럼 다음에 또 만나요~👋🏻
안녕하세요 라라! 😊 리뷰해주셔서 감사합니다!
저번에 꼼꼼하게 리뷰해주신 덕분에 이번 미션에서 리팩토링할 점이 많았고 그 과정에서 새롭게 배운것도 많습니다!
이번에도 아낌없는 코멘트 부탁드립니다 🙇 감사합니다!
체크 리스트
test를 실행했을 때, 모든 테스트가 정상적으로 통과했나요?객체지향 생활체조 요구사항을 얼마나 잘 충족했다고 생각하시나요?
1~5점 중에서 선택해주세요.
선택한 점수의 이유를 적어주세요.
객체 지향적인 코드-자신이 가진 데이터를 스스로 책임지는 응집력 있는 객체 만들기-에 매우 집중하며 코드를 구현했습니다!
어떤 부분에 집중하여 리뷰해야 할까요?
이번 미션에선 저번 코멘트를 바탕으로 객체 간 결합도를 줄이고 응집력 있게 만드는 것에 매우 집중했습니다!
특히 상속 관계는 정말 필요할 때만 사용하고,
조합과 인터페이스를 활용해 최대한 유연하면서도 중복이 없는 코드를 만들고자 노력했습니다.
아래 집중 구현한 포인트와 궁금한 점을 코멘트로 추가했으니, 확인 후 아낌없는 코멘트 해주시면 감사하겠습니다!! 😊👍