-
Notifications
You must be signed in to change notification settings - Fork 467
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
[2단계 - 블랙잭 베팅] 몽이(황재웅) 미션 제출합니다. #875
base: wodnd0131
Are you sure you want to change the base?
Conversation
-test: 테스트를 위한 MockParticipant 생성
test: 배당 로직으로 변경할 RoundHistory 테스트 삭제
docs: 코드 설명을 위한 문서화 추가
private void processPlayerHits( | ||
Participant<? extends Role> player, | ||
final BlackjackGame blackjack | ||
) { | ||
final var name = player.getName(); | ||
while (player.isHit() && inputView.readPlayerAnswer(name)) { | ||
hitByParticipant(blackjack, player); | ||
outputPlayerHand(player); | ||
player = blackjack.hitByParticipant(player); | ||
final List<TrumpCard> hand = player.getCards(); | ||
outputView.printPlayerHand(player.getName(), converter.handToText(hand)); | ||
} | ||
} |
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.
이 부분이 View와 도메인이 강하게 상호 작용하는 부분입니다.
이 로직을 수정하기 위해서 함수형 인터페이스를 고려하였지만,
결과적으로 도메인에 View가 의존하는 구조가 되었습니다.
때문에, 현재 구조에서 Getter로 도메인 객체가 나오는 것이 왜 좋지 않은지 고민해보았습니다.
저는 이를 불변성이 보장되지 않았다는 것을 가장 큰 이유로 보았습니다.
또한, 객체를 인터페이스 (Participant<? extends Role>) 를 통해 반환 하였기에
캡슐화의 측면도 다소 회복할 수 있었습니다.
이 문제에 대해서는 트레이드 오프의 문제라 생각합니다.
-> 무엇이 문제고, 상응하는 장점이 있는가
=> 객체가 외부에 노출 / 코드의 복잡도가 낮아지고, 도메인이 view의 책임을 가지지 않음
그리고 트레이드 오프 문제는 정답을 구하는 것이 아니기에,
상대방도 이 정도면 괜찮다! 할만한 근거가 충분해야 한다고 느꼈어요!
-> 문제 상황에 잘 대비하였는가?
=> 불변성을 보장 (+캡슐화 강화)
이러한 측면에서, 트레이드 오프 관계를 잘 인지하고 코드에 반영한 걸까요?
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.
안녕하세요~앤지!🙌
다시 만나서 반가워요!
이번 2단계 미션에서는 마지막 피드백 반영을 중점으로 다루었어요!
혹시 이 과정에서 현재 미션의 학습 방향과 너무 틀어진 점이 있거나,
객체 지향에서 아쉬운 부분이 있다면, 피드백 부탁드려요!😎
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.
안녕하세요 몽이~
2단계도 잘 구현해주셨네요. 😄
코멘트 몇 개 남겼으니 확인 부탁드립니다~! 👍
} | ||
|
||
public void hit(final TrumpCard card) { | ||
public Participant<? extends Role> hit(final TrumpCard card) { | ||
isHit(); |
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.
안녕하세요! 앤지 좋은 리뷰감사해요~🙌
hit()는 participant.intialDeal()과 Participants에 의한 호출로 사용되고 있습니다.
그럼 해당 피드백은 intialDeal()에서 hit의 return 값이 사용되지 않기 때문일까요?🤔
public Participant<T> initialDeal(final Deck deck) {
for (int i = 0; i < INITIAL_DEAL_CARD_COUNT; i++) {
final TrumpCard card = deck.draw();
hit(card);
}
return new Participant<>(this.role, getCards());
}
여기서, 두 가지 문제점들을 발견했어요.
- hit()가 반복마다 방어적 복사를 하여 불필요한 객체를 선언한다.
- hit()는 객체 본인의 상태 변경을 한 이후, 방어적 복사를 한다. (본래 도메인 객체에 영향을 준다.)
intialDeal()는 Participants의 정적 생성자 로직에 의해 호출되기 때문에,
Participants의 정적 생성자에서 개별 Participant마다 총 4번 Particpant를 선언하고 있네요.😅
또한, 딜 시작할 때는 hit하지 못할 경우가 없기 때문에,
Particpant가 직접 Deck의 카드를 뽑아야 하는 책임이 없죠.
때문에, 해당 메서드를 삭제하고, Deck이 직접 초기 카드 2장을 반환하도록 생성 과정을 수정하였습니다!
추가로 hit 의 리턴 타입에 불필요한 와일드 카드 선언도 수정하였습니다. 🫡
isHit(); | ||
hand.add(card); | ||
return new Participant<>(role, getCards()); |
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.
hit() 메서드를 호출하는 곳에서 새롭게 만든 Participant
를 사용하지 않고 있는 것 같아요.
어떤 처리를 해보려고 했던 걸까요?
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.
hit()는 participant.intialDeal()과 Participants에 의한 호출로 사용되고 있어요!
participant.intialDeal()의 경우, 위 답변에서 문제점을 확인하여 수정했습니다!🫡
Participants에 의한 호출의 경우, 새롭게 만든 Participant를 사용하고 있습니다.
현재 코드에서 BlackjackGame으로부터 요청된 처리는 불변성을 지키고자 방어적 복사로 반환합니다.
하지만, 위 코드와 같이 현재 객체의 상태를 직접 조작한 후 방어적 복사를 하고 있죠.
본래 객체에 대한 불변성을 지키지 않고, 단순히 복사만 하여 설계 목적에 맞지 않습니다.🤔
때문에, 내부 상태를 직접 수정하는 것이 아닌, 생성을 통하도록 수정하였습니다.🙌
final Participant<? extends Role> player, | ||
final Participant<? extends Role> dealer | ||
) { | ||
final var score = player.calculateScore(); |
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.
final var score = player.calculateScore(); | |
final var playerScore = player.calculateScore();' |
score 비교 로직이 Player안에 있었다면 score라고 나타내도 좋겠지만,
RoundResult에 있으니 Player의 score는 playerScore
로 나타내면 좋을 것 같아요.
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.
RoundResult에 있으니 Player의 score는 playerScore로 나타내면 좋을 것 같아요.
1단계 초기 코드에서, 해당 메소드를 Player가 가지고 있었습니다.
리펙토링하면서, 객체를 새로 할당했음에도 Player에 대한 Dealer와의 계산이라 생각했어요.
하지만 두 참가자에 대한 Round, 그리고 그 결과를 다루는 것이 RoundResult의 책임이죠.
두 참가자를 구별할 수 있게, 피드백처럼 수정하는 게 좋은 것 같아요.
네이밍에서 객체의 책임도 혼용하지 않도록 고려했어야 했군요!🤔
if (score.isBust()) { | ||
return RoundResult.LOSE; | ||
} | ||
if (score.equals(dealerScore)) { | ||
return RoundResult.PUSH; | ||
} | ||
if (player.isBlackjack()) { | ||
return RoundResult.BLACKJACK; | ||
} | ||
if (dealerScore.isBust() || score.isGreaterThan(dealerScore)) { | ||
return RoundResult.WIN; | ||
} | ||
return RoundResult.LOSE; | ||
} |
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.
함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
RoundResult.round()가 이에 반합니다.
이것의 해결책으로 테크코스 피드백의 상태 패턴이 있음을 알게 되었으나,
PR 제출 기간이 더 늦어지지 않기 위해 우선 PR 제출하였습니다.
상태 패턴의 적용을 좀 더 고민해본 이후 다루겠습니다.
관련하여 조언해주실 수 있는 부분이 있다면 피드백 부탁드리겠습니다.🫡
네! 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 하기 위해 상태 패턴 적용을 고민하고 계시군요.
메서드의 길이를 제한하는 이유는 하나의 메서드가 단일한 역할을 수행하도록 만들기 위함입니다.
지금의 메서드는 여러 개의 조건문을 포함하고 있어 다소 복잡해 보일 수 있지만, 블랙잭 게임의 결과를 반환하는 역할을 명확하게 수행하고 있어 보여요.
즉, 게임의 결과를 판단하는 로직이 한곳에 모여 있어 오히려 코드의 흐름을 쉽게 파악할 수 있는 것 같아요.
만약 상태 패턴을 적용한다면, 각 플레이어의 상태를 별도의 클래스로 분리하여 관리할 수 있습니다.
이렇게 하면 각 상태에 따른 결과 판단 로직이 개별 클래스로 이동하여 코드가 더욱 객체지향적으로 구성될 수 있지만, 반대로 로직이 여러 클래스로 분산되어 한눈에 파악하기 어려울 수도 있습니다.
이번 미션에서는 몽이가 상태 패턴 적용의 장단점을 고민해보고 적용할지 여부를 판단해보는 것도 좋은 경험이 될 것 같아요. 어떤 방법을 선택하든 찬성이니 각 방법의 장단점을 비교하여 가장 적절한 방법을 선택해보세요. 😄
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.
지금의 메서드는 여러 개의 조건문을 포함하고 있어 다소 복잡해 보일 수 있지만, 블랙잭 게임의 결과를 반환하는 역할을 명확하게 수행하고 있어 보여요.
제가 "객체지향 생활 체조를 지키지 못하였는가?"에 잘못 얽매는 것일 수도 있겠군요..!
사실 테크코스의 피드백을 보고,
상태 패턴이 가지는 객체스러움..?이 매력적이라 다루어 보고 싶었던 마음도 컸던 것 같아요.😅
지금 코드가 상태 패턴을 적용하지도 않았고, 객체 지향적으로 부족할 순 있다고 생각해요.
하지만, 지금이 직관적이라는 장점 또한 중요하다 생각해요!
아쉬울 순 있더라도, 이 메서드는 명확한 책임을 가졌다는 것은 분명한 것이죠..!
그렇기에, 지금 상태 패턴을 고려하는 것은 단순히 객체 지향적인 코드를 작성한다와는 조금 다른 것 같아요!
이 메서드 하나를 위해 전체 설계를 다시 갈아 엎을 것인가?
이렇게 되면 트레이드 오프 관계를 달리 생각하게 되었어요.🤔
처음처럼 상태 패턴이 가치 있다고 느끼지 않게 된 것이죠.
그럼에도 상태 패턴을 다룬다면, 단순히 요구사항 때문인가?
사실 코드에 바로 적용해볼까 생각했었는데,
저 조건문들을 어떻게 분리해 객체로 만들지 막막하더라구요.
상태 패턴으로 묶어야 하는 것들이 여기저기 퍼져있었어요.
적어도 기존 설계에서 이에 대한 명확한 책임을 고려하지 않았던 것이죠.
"명확한 책임 분배를 할 수 있는가"
이것이 제게 부족했던 것, 즉 요구사항을 만족하지 못했던 부분이라 생각해요.
그리고 이것은 지금 코드를 갈아엎어서 상태 패턴 하나 적용해서 배울 수 없다고 생각합니다.
패턴을 배우는 것일 뿐이니까요.
지금 이러한 딜레머를 버티며 패턴 적용에 많은 투자하는 것보다,
다음 미션에 앞서서, 오브젝트나 객사오같은 전공 도서를 좀 더 읽어보는 것도
객체 지향을 이해하는 데 도움이 되지 않을까..싶네요.🤔
분명 상태 패턴은 유의미할 것이고, 교육 과정을 제대로 못 따라가는 건지 걱정되지만,,
지금 부족한 능력을 키우기 위해 집중할 방법은 무엇일지 고민해봐야겠어요!😊
@@ -10,20 +9,20 @@ | |||
import java.util.Queue; | |||
import java.util.stream.IntStream; | |||
|
|||
public class Deck { | |||
public final class Deck { |
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.
2단계를 진행하시면서 대부분의 클래스에 final 키워드가 붙었는데 어떤 이유에서 추가하셨을까요?
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.
클래스에 final을 선언하는 것은, 해당 클래스의 상속을 막기 위함입니다.
클래스의 동작이 명확하게 정의되어 있고, 확장이 필요 없을 때 사용합니다.
현재 제 설계에서, 인터페이스를 상속하는 경우를 제외하면 클래스 자체를 상속이 필요하지 않습니다.
상속을 통한 확장이 아니라 컴포지션을 토대로 새로운 객체를 주입해 확장할 수 있기 때문이죠.😎
때문에 이 의도를 좀 더 명확히 하기 위해, final 클래스로 선언하였습니다.
만약, 새로운 기능을 추가해야 한다면,
final 때문에라도 상속 이외의 현재 설계에 맞는 방법을 고려할 보험이 될 거에요!😄
그렇다면 설계 의도 외에도, 상속은 원천 봉쇄당할 정도로 나쁜가?
앞서 이야기한 것처럼 Role(인터페이스)를 상속받아 다형성을 활용하는 것은 좋은 코드라 생각합니다.
(좋은 리스코프 치환 법칙의 예이죠!)
하지만 상속, 그 중에서도 단순히 부모 클래스로부터 자식을 상속하는 경우가 있습니다. (구현 상속)
class Manuscript {
protected String body;
void print(Console console) {
console.println(this.body);
}
}
class Article extends Manuscript {
void submit(Conference cnf) {
cnf.send(this.body);
}
}
이 경우, 두 클래스가 강하게 연결되어 객체 간 경계와 캡슐화가 약해졌습니다.
객체는 자율적이어야 하고, 그것에 책임이 있어야 한다 이야기합니다.
위 코드에서 body를 공유하는 것을 보면,
Article은 body에 대한 책임도 가지고 있지 않으며,
Manuscript은 body에 대한 책임을 자식에게 공개하고 있습니다.
이는 마치 데이터와 행동을 분리된 정적 유틸리티 클래스와 유사하게 느껴졌어요.🤔
단순히 코드 재사용을 위해, 객체의 책임을 모호하게 만든다 생각합니다.
Abstract를 사용하는 경우도, 이와 유사한 단점을 가지고요.
이 점이 명확하지 않아 1단계에서 name이라는 필드를 부모 자식 중 누가 가져야 하나 고민했던 것 같습니다.
때문에, 스스로 행동과 상태를 관리하는 주체가 되도록 컴포지션을 도입하게 되었구요.😎
이러한 맥락에서, 설계 의도와 객체의 책임을 명확히 한다 생각되는 클래스를 final 선언했습니다!
src/main/java/domain/card/Deck.java
Outdated
final List<TrumpCard> deck = IntStream.range(0, numberOfDeck) | ||
public static Deck createShuffledDecks(final int numberOfDeck) { | ||
List<TrumpCard> deck = new ArrayList<>(IntStream.range(0, numberOfDeck) |
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.
질문이 있습니다!
List 자료형에 final을 붙였을 때와 안붙였을 때 어떤 차이점이 있을까요?
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.
final 키워드는 변수의 참조가 한 번 할당된 후에는 변경될 수 없음을 의미합니다!
그러나 여기서 중요한 점은 final이 참조 변경을 금지할 뿐,
참조된 객체의 내용(내부 상태) 변경은 금지하지 않는다는 것이죠.
원시 자료형들과 달리 List 자료형과 같은 경우, 내부 상태를 별도로 가지고 있습니다.
그래서 선언된 List 자체의 참조를 바꾸진 않지만, List 내부에 대한 변경이 가능하죠!👊
이 점 때문에 사이드 이펙트가 발생할 수 있어, 방어적 복사와 같이 불변성 보장을 고려하게 됩니다.
위 변경에서 final을 제거한 것은 이러한 맥락을 잘못 이해했기 때문이죠.😅
Collections.shuffle(list) -> 리스트를 섞고 아무것도 반환하지 않음
저는 final 선언을 읽는 입장에서 "해당 객체가 변하지 않음!" 으로 여길 수 있을 때 사용했습니다.
그래서, shuffle 메서드가 List를 변경하니, final을 사용하는 것은 명시적이지 않다 생각했습니다.
하지만 final의 기능으로 보완하는 영역이 아닌 것이죠!
제가 final에 대한 이해가 다소 모호해 본래의 장점을 활용하지 못했던 것 같습니다!
좋은 지적 감사해요!🫡
/** | ||
* 블랙잭 게임의 모든 참가자(딜러와 플레이어들)를 관리하는 클래스입니다. | ||
* <p> | ||
* 내부적으로는 딜러와 플레이어를 구체적인 타입으로 구분하여 관리하지만, | ||
* <p> | ||
* 외부로는 다형적 인터페이스(Participant<? extends Role>)를 제공합니다. | ||
*/ | ||
public final class Participants { |
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를 적절히 구별할 수 있어야 한다는 것이 고민이었습니다.제네릭은 어떻게 재사용성과 강력한 타입 검증이라는 상반될 수 있는 목표를 동시에 달성하려고 하는가?
Prolog에서 다루는 이 문제가 현재 저의 고민과 같은 맥락이라 느꼈습니다.
때문에 상속의 단점을 줄이고, 다형성의 장점은 이어가기 위해 제너릭을 활용하여 컴포지션 패턴을 사용했습니다.
Participants를 기준으로 대한 내부에서 사용될 때는 개별 객체로 구분하고,
외부 사용될 땐 다형성으로 활용할 수 있도록 하였습니다.이 구조에서 고려할 점은 어떤 것이 있을지,
그리고 목적을 적절히 만족한 코드인지 피드백을 받고 싶습니다.🦆
Participants를 통해 제네릭을 활용하여 중복을 줄이고, Dealer와 Player를 적절히 구분하면서 타입 안정성을 확보하려는 시도군요. 좋은 접근입니다.
이번 시도를 통해 어떤 이점을 얻었다고 생각하시나요? 현재 구조는 분명 장점이 있지만, 코드의 복잡성을 증가시키고 가독성을 낮출 가능성도 있습니다. 만약 필요 이상으로 복잡해지고 있다면, 인터페이스 기반 다형성을 활용하거나 기존의 상속 구조를 유지하는 것만으로도 충분히 문제를 해결할 수 있습니다.
중복을 줄이면서 Dealer와 Player의 차이를 효과적으로 구분할 수 있는가? 라는 질문을 다시 한 번 고민해보고, 현재 구조가 가장 직관적이고 유지보수하기 쉬운 방식인지 검토해보는 것도 좋을 것 같습니다. 😊
outputView.printPlayerRoundResult(participants.getName(), convertedCards, score); | ||
private void outputParticipantHandResult(final Participant<? extends Role> participant) { | ||
final var convertedCards = converter.participantCardToText(participant.getCards()); | ||
final var score = participant.calculateScore(); |
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.
- Getter를 안전하게 사용했는가?
상태 변경이 필요할 때마다 새로운 객체를 생성하여 불변성을 유지합니다.
이를 통해 Getter 사용이 현재 도메인에 영향을 주지 않도록 합니다.이번 미션이 가지는 view와의 반복적인 상호작용으로 인해,
불가피하게 Getter를 통해 컨트롤러로 Blackjackgame 내부 객체를 꺼내야하는 상황이 생겼습니다.구조 자체를 바꿀 수도 있지만,
저는 Getter를 사용해도 괜찮다는 것을 명확히 하기로 했습니다.
때문에, Getter 사용에 다음과 같은 규칙을 두었습니다.
- Getter는 불변성을 지켜 사용해야 한다.
불변성을 통해 마치 DTO처럼 도메인과 분리된 값을 전달하기 위해 객체를 이용하고자 했습니다.
2. Getter로 추출된 객체는 BlackjackGame의 내부 객체에 사용되지 않는다.BlackjackGame 내부 객체를 추출된 객체로 직접 조작할 수 없습니다. ( = Setter 사용 금지 )
값을 변경하기 보다는 생성자로 생성합니다.
이와 같이 Getter를 사용하는 조건으로 불변성을 특히 지켜보고자 했습니다.불변성을 제대로 의식한 것이 처음이기에,
이러한 기준의 방향성은 적절한지,
또한, 불변성은 제대로 지켜졌는지 조언 부탁드립니다.😄
3.객체의 책임 분배가 잘 이루어졌는가?
이전 코드에서 BlackjackGame 객체를 비롯하여, 객체의 책임이 다소 모호했던 것을 발견했습니다.
객체의 책임에 대한 접근이 아직 미숙하다 생각합니다.
책임이 모호한 부분이 있다면 간단한 부분이라도 지적 부탁드립니다.👊
getter는 불변성을 지켜 사용해야 한다 와 getter로 추출된 객체는 내부 객체에 사용되지 않는다 라는 두 가지 원칙을 정한 점이 아주 좋습니다 👍
다만 participant.calculateScore()는 상위 메서드에서 blackjack.getParticipant();
getter로 가져온 뒤 수행되고 있는데, 이처럼 객체 내부 상태는 변경하지 않지만 내부 상태를 기반으로 의미있는 조회성 로직을 수행하는 것도 getter 사용을 지양하면 어떨까요?
또 getter를 사용할 때 getter가 반환하는 객체 자체도 불변성을 유지하는지(예: List) 확인해보고 또 불변 객체 생성이 성능에 어떤 영향을 미칠지에 대해서도 한번 고민해보면 좋을 것 같아요 😄
체크 리스트
test
를 실행했을 때, 모든 테스트가 정상적으로 통과했나요?객체지향 생활체조 요구사항을 얼마나 잘 충족했다고 생각하시나요?
1~5점 중에서 선택해주세요.
선택한 점수의 이유를 적어주세요.
1단계에서는 중복 코드 제거를 위해 추상 클래스를 사용했습니다.
이번 리펙터링에서는 제너릭을 기반으로 컴포지션을 적용했습니다.
이는 1단계 PR 마지막에서 다룬 "추상화된 Dealer와 Player를 어떻게 구분할까?"에 대한 선택입니다.
만약 두 객체를 나누어 개별 타입으로 관리한다면,
participant를 상속 받은 자식이라는 것을 알기 어렵습니다.
제너릭 컴포지션으로, Participant가 가진 역할(객체)를 명확히 합니다.
또한, 제너릭 와일드 카드를 통해 여전히 다형성을 활용할 수 있습니다.
Dealer와 Player를 구분해야 한다면,
상속이 가지는 단점인 강연결을 풀 수 있는 컴포지션이 좀 더 유리하다 판단하였고,
여전히 상속이 가지는 다형성의 장점은 유효하다 느껴 이를 활용해보았습니다.
1단계의 초기 컴포지션 때와 달리,
인스턴스 변수를 3개 이상 가진 객체를 두지 않았습니다.
RoundResult.round()가 이에 반합니다.
이것의 해결책으로 테크코스 피드백의 상태 패턴이 있음을 알게 되었으나,
PR 제출 기간이 더 늦어지지 않기 위해 우선 PR 제출하였습니다.
상태 패턴의 적용을 좀 더 고민해본 이후 다루겠습니다.
관련하여 조언해주실 수 있는 부분이 있다면 피드백 부탁드리겠습니다.🫡
어떤 부분에 집중하여 리뷰해야 할까요?
1. 새로 적용한 컴포지션은 유효한가?
이번 미션은 이전의 추상 클래스 기반 상속으로도 충분하다고 생각합니다.
하지만, 저는 이번 미션에서 "중복 코드를 어떻게 관리할 것인가"라는 요구사항에 집중했습니다.
현재 코드 중복을 줄여 재사용성을 높혀야 하며,
동시에 Dealer와 Player를 적절히 구별할 수 있어야 한다는 것이 고민이었습니다.
Prolog에서 다루는 이 문제가 현재 저의 고민과 같은 맥락이라 느꼈습니다.
때문에 상속의 단점을 줄이고, 다형성의 장점은 이어가기 위해 제너릭을 활용하여 컴포지션 패턴을 사용했습니다.
Participants를 기준으로 대한 내부에서 사용될 때는 개별 객체로 구분하고,
외부 사용될 땐 다형성으로 활용할 수 있도록 하였습니다.
이 구조에서 고려할 점은 어떤 것이 있을지,
그리고 목적을 적절히 만족한 코드인지 피드백을 받고 싶습니다.🦆
2. Getter를 안전하게 사용했는가?
상태 변경이 필요할 때마다 새로운 객체를 생성하여 불변성을 유지합니다.
이를 통해 Getter 사용이 현재 도메인에 영향을 주지 않도록 합니다.
이번 미션이 가지는 view와의 반복적인 상호작용으로 인해,
불가피하게 Getter를 통해 컨트롤러로 Blackjackgame 내부 객체를 꺼내야하는 상황이 생겼습니다.
구조 자체를 바꿀 수도 있지만,
저는 Getter를 사용해도 괜찮다는 것을 명확히 하기로 했습니다.
때문에, Getter 사용에 다음과 같은 규칙을 두었습니다.
1. Getter는 불변성을 지켜 사용해야 한다.
2. Getter로 추출된 객체는 BlackjackGame의 내부 객체에 사용되지 않는다.
이와 같이 Getter를 사용하는 조건으로 불변성을 특히 지켜보고자 했습니다.
불변성을 제대로 의식한 것이 처음이기에,
이러한 기준의 방향성은 적절한지,
또한, 불변성은 제대로 지켜졌는지 조언 부탁드립니다.😄
3.객체의 책임 분배가 잘 이루어졌는가?
이전 코드에서 BlackjackGame 객체를 비롯하여, 객체의 책임이 다소 모호했던 것을 발견했습니다.
객체의 책임에 대한 접근이 아직 미숙하다 생각합니다.
책임이 모호한 부분이 있다면 간단한 부분이라도 지적 부탁드립니다.👊