Repository navigation
[2단계 - 블랙잭(베팅)] 후디(조동현) 미션 제출합니다. - #369
Conversation
…서 실행하도록 구조 개선 - BlackjackController의 public 메소드 이름 개선
- Hand.getScore 를 사용하는 코드를 Score.calculateSumFrom 으로 변경 - 테스트코드 수정
- isGreaterThan, isLessThan 과 같은 메소드를 없애고, isBusted, isOverDealerHitThreshold 와 같은 메소드를 별개로 구현함
…atchResult 로 위임 - Dto 였던 DealerMatchDto 를 DealerMatchResult 라는 도메인으로 변경 - Dto 였던 PlayerMatchDto 를 PlayerMatchResult 라는 도메인으로 변경
- 테스트 코드 작성 - Score 클래스가 카드 리스트를 전달받아 합계를 계산할 수 있도록 개선 - Hand 클래스가 첫 두장의 카드를 리스트로 반환하는 기능 구현
- 테스트코드 작성 - Money 도메인 구현 - BetAndProfit 도메인 구현
- Money 도메인에 add, subtract 메소드 구현
- BlackjackController 및 InputView, OutputView 기능 구현 - 승패결과를 나타내는 클래스 제거 - 기타 사용되지 않는 클래스 제거
- PlayerBetResult 에서 ProfitResult 로 이름 변경 - DealerProfitResult 제거
- Player 클래스에 betMoney 필드 추가 - Player 생성자 및 of 메소드에 betMoney 파라미터 추가 - ResultType 에 WIN_WITH_BLACKJACK 상수 추가 - ResultType 에 승부 결과 별 수익 계산 로직 추가
- Players 일급 컬렉션 클래스 구현 - Dealer 와 Players 를 가지고 있는 ParticipantGroup 클래스 구현 - BlackjackGame 이 Dealer 와 List<Player> 대신 ParticipantGroup 을 갖도록 개선 - ParticipantDto 를 추상클래스로 변경 - DealerDto 와 PlayerDto 를 ParticipantDto 를 상속하여 구현 - getCardNames 를 ParticipantDto 에서 Hand 로 이동 - ProfitResult 클래스 제거 - BetAndProfit 클래스 제거 - 변경된 도메인에 맞게 BlackjackController 와 OutputView 로직 수정
- 테스트 코드 수정 - Score 가 다른 Score 에 비해 큰지, 작은지를 검사하는 isGreaterThan, isLessThan 메소드를 구현함
| public class Player extends Participant { | ||
|
|
||
| private static final String INVALID_NAME_LENGTH_EXCEPTION_MESSAGE = "이름은 1글자 이상이어야합니다."; | ||
|
|
||
| private Player(final String name, final Hand hand) { | ||
| private final Money betMoney; | ||
|
|
||
| private Player(final String name, final Hand hand, final Money betMoney) { |
There was a problem hiding this comment.
Participant 와 Player 는 별개의 클래스이고, Participant 가 가지고 있는 두개의 필드 (playerName, hand)는 private 으로 Player 측에서 접근하지 못하므로 Player 의 인스턴스 변수는 하나라고 생각했습니다.
| static { | ||
| // TODO: 2 Depth 인가?... | ||
| cache = Stream.of(CardSymbol.values()) | ||
| .flatMap(symbol -> Stream.of(CardRank.values()) | ||
| .map(rank -> new Card(rank, symbol))) | ||
| .collect(Collectors.toSet()); | ||
| } |
There was a problem hiding this comment.
이 코드를 for 문으로 작성할 경우 깊이가 2가 되어버려, Stream 을 2중으로 사용했는데요, 결국 조삼모사가 아닌가 싶어요! 앨런의 의견이 궁금합니다!
|
|
||
| public List<Card> getInitialCards() { | ||
| return cards.subList(0, INITIAL_CARDS_COUNT); | ||
| } | ||
|
|
There was a problem hiding this comment.
이번 미션의 경우 시작과 없음 등을 의미하는 0 이라는 숫자는 별도로 상수로 분리하지 않았습니다. 상수로 분리하지 않은 0 의 경우 해당 코드 자체가 길지 않고, 메소드명으로 충분이 0 이 어떤 역할을 하는지 유추할 수 있을 것 이라고 생각했는데요, 앨런의 의견이 궁금합니다!
hongbin-dev
left a comment
There was a problem hiding this comment.
안녕하세요 후디!
2단계 미션 고생하셨어요~ 구조가 많이 바뀌었군요 😮
몇 가지 코멘트 남겼으니 확인해주세요
| static { | ||
| // TODO: 2 Depth 인가?... | ||
| cache = Stream.of(CardSymbol.values()) | ||
| .flatMap(symbol -> Stream.of(CardRank.values()) | ||
| .map(rank -> new Card(rank, symbol))) | ||
| .collect(Collectors.toSet()); | ||
| } |
| return initialScore.equals(Score.valueOf(Score.MAXIMUM_SCORE)); | ||
| } | ||
|
|
||
| protected ResultType compareWith(Participant other) { |
There was a problem hiding this comment.
| protected ResultType compareWith(Participant other) { | |
| protected ResultType compareWith(Dealer other) { |
other에 Player가 들어가도 무관한가요?
There was a problem hiding this comment.
딜러가 플레이어와 비교할 경우가 있을 것이라고 생각해서 이렇게 구현했는데, 생각해보니 Dealer 로 타입을 제한해도 될 것 같아요. 수정했습니다 :)
| if (other.isBlackjack()) { | ||
| return ResultType.DRAW; | ||
| } | ||
| if (other.isBusted()) { | ||
| return ResultType.WIN_WITH_BLACKJACK; | ||
| } | ||
| return ResultType.WIN_WITH_BLACKJACK; |
There was a problem hiding this comment.
| if (other.isBlackjack()) { | |
| return ResultType.DRAW; | |
| } | |
| if (other.isBusted()) { | |
| return ResultType.WIN_WITH_BLACKJACK; | |
| } | |
| return ResultType.WIN_WITH_BLACKJACK; | |
| if (other.isBlackjack()) { | |
| return ResultType.DRAW; | |
| } | |
| return ResultType.WIN_WITH_BLACKJACK; |
이렇게 간소화할 수 있지 않을까요?
| if (isBlackjack()) { | ||
| return getResultTypeIfBlackjack(other); | ||
| } | ||
|
|
||
| if (isBusted()) { | ||
| return getResultTypeIfBusted(other); | ||
| } | ||
|
|
||
| return getResultTypeIfOrdinary(other); |
| if (other.isBlackjack()) { | ||
| return ResultType.LOSE; | ||
| } | ||
| if (other.isBusted()) { | ||
| return ResultType.DRAW; | ||
| } | ||
| return ResultType.LOSE; |
There was a problem hiding this comment.
| if (other.isBlackjack()) { | |
| return ResultType.LOSE; | |
| } | |
| if (other.isBusted()) { | |
| return ResultType.DRAW; | |
| } | |
| return ResultType.LOSE; | |
| return ResultType.LOSE; |
플레이어가 버스트면 항상 패배입니다
There was a problem hiding this comment.
블랙잭 규칙을 잘 못 이해하고 있었네요 ㅎㅎ; 로직을 변경했습니다!
| public class Players { | ||
|
|
||
| private static final String NOT_FOUND_PLAYER_EXCEPTION_MESSAGE = "플레이어를 찾을 수 없습니다."; | ||
| private final List<Player> value = new ArrayList<>(); | ||
|
|
||
| public void add(Player player) { | ||
| validateNotDuplicateName(player.getName()); | ||
| value.add(player); | ||
| } |
There was a problem hiding this comment.
하나씩 추가하는형태(add() 메서드)가 아니라, 한번에 입력받아서 생성할 수는 없을까요?
그래야 불변을 유지하기 좋을 것 같네요
There was a problem hiding this comment.
플레이어 이름과 베팅 금액을 한쌍으로 여러개를 전달받아야, 한번에 플레이어 여러명을 생성하고 Players 를 생성할 수 있습니다. 고민을 많이 해봤는데 플레이어를 생성하기 위한 PlayerCreateDto 를 컨트롤러에서 생성해서 BlackjackGame 에 전달하는 형태로 개선했습니다!
| Money totalProfit = Money.from(0); | ||
| for (Player player : value) { | ||
| totalProfit = totalProfit.add(player.calculateProfit(dealer)); | ||
| } | ||
|
|
||
| return totalProfit; |
There was a problem hiding this comment.
이쪽은 스트림으로 분리할 수 있을 것 같아요. 분리하면 totalProfit 같은 가변변수를 줄일 수 있을 것 같네요.
There was a problem hiding this comment.
return value.stream()
.map(player -> player.calculateProfit(dealer))
.reduce(Money.from(0), Money::add);위와 같이 수정하였습니다 ! ㅎㅎ
| public class Money { | ||
| private static final double BLACKJACK_PROFIT_RATE = 1.5; | ||
|
|
||
| private final int value; |
There was a problem hiding this comment.
금액을 사용하는거니 Decimal 타입을 사용해볼까요?
https://tecoble.techcourse.co.kr/post/2021-04-26-BigInteger_BigDecimal/ 이쪽을 참고해보면 좋을 것 같아요
There was a problem hiding this comment.
처음 들어본 클래스네요! 금액과 같이 정확한 계산이 필요한 경우 BigDecimal 을 사용해야 하는군요. 지식 공유 감사드립니다 😄
- compareWith 로직을 Participant 에서 Player 로 변경 - 테스트 코드 변경
- Controller 에 잘못 입력된 베팅금액에 대한 재입력 로직 추가 - getValue 를 getIntValue 로 변경
|
|
||
| public int getIntValue() { | ||
| return value.intValue(); | ||
| } | ||
|
|
There was a problem hiding this comment.
value 의 타입을 int 에서 BigDecimal 로 변경하였으므로 명시적으로 메소드명에 'int' 라는 단어를 사용하였습니다.
hongbin-dev
left a comment
There was a problem hiding this comment.
안녕하세요 후디!
2단계 미션 진행하느라 고생많으셨어요! 코드가 많이 깔끔해졌네요.
이만 머지하겠습니다 👏
| public int getIntValue() { | ||
| return value.intValue(); |
There was a problem hiding this comment.
프로퍼티에 int가 명시되어있는데, 자료형을 메서드이름에 남길 필요가 있을까요?
There was a problem hiding this comment.
아 프로퍼티에도 명시되어 있고, 반환타입에도 명시가 되어 있군요!.. 수정해야할 것 같습니다!
| public Player generatePlayer(String playerName, int betMoney) { | ||
| return Player.of(playerName, generateInitialHand(), Money.from(betMoney)); |
| public void addPlayer(Player player) { | ||
| players.add(player); | ||
| } |
| BlackjackGame blackjackGame = initializeGame(); | ||
| printInitialHand(blackjackGame); | ||
| giveExtraCardsToAllPlayer(blackjackGame); | ||
| giveExtraCardsToDealer(blackjackGame); | ||
| printFinalHandAndScore(blackjackGame); | ||
| printBetResult(blackjackGame); |
There was a problem hiding this comment.
giveExtraCardsToAllPlayer(blackjackGame)
위 같은 메서드를 봤을 때, 코드 사용자 입장에서 파라미터로 넘어간 blackjackGame의 상태가 바뀔거라고 예상하지못하는데요. 이런 부분을 개선해보셔도 좋을 것 같네요.
There was a problem hiding this comment.
저도 그 부분이 불편했었는데, 완벽히 blackjackGame 을 불변으로 만들거나 메서드명을 개선해야할 것 같습니다!..
안녕하세요, 앨런! 이번 피드백도 잘 부탁드려요!
1단계 코멘트에 대한 답변과 추가 질문은 이전 PR에 남겨두었습니다! (#273)
이전 두 미션보다 이번 미션이 훨씬 오래 걸렸습니다. 도메인 설계를 몇번을 엎은지를 모르겠네요. 🥲
금요일 강의 때 상태 패턴을 사용한 설계에 대해서 배우게 되었는데, 적용해보고 싶었으나 일단 지금 해온 설계를 마무리 해보고싶어서 아쉽게도 상태 패턴을 적용해보지는 못했습니다.
인스턴스 멤버를 2개로 제한하는 요구사항덕분에 도메인 설계에서 참 오래 걸렸던 것 같아요.
추가적인 질문이나 이야기해보고 싶은 내용은 코멘트로 작성할게요 🙇 즐거운 주말 보내시길!
아쉬운 점
Players,ResultCount등을 불변 객체로 만들면 더 좋았을텐데, 일정의 문제로 가변 객체로 둔 것이 아쉽습니다.