우아한테크코스 7기/회고

[레벨1] 블랙잭 미션 회고록

민똔 2025. 4. 8. 18:39

블랙잭 미션부터 레벨 인터뷰까지 정신없는 하루하루를 보내다보니 회고글 업로드가 많이 늦어졌다 ..😓
 
솔직히 말하자면 블랙잭 미션은 다른 미션에 비해서 인사이트를 얻은게 크게 없다보니 이번 포스팅은 짧게 작성하고 마무리하려고 한다.
그 대신 이후에 업로드할 글들(장기 미션, 레벨 인터뷰, 레벨1 회고 ...)에서 더 많은 내용을 기록할 계획이다.
 

Pull Requests

1단계

https://github.com/woowacourse/java-blackjack/pull/831

[1단계 - 블랙잭] 밍곰(박민선) 미션 제출합니다. by minSsan · Pull Request #831 · woowacourse/java-blackjack

체크 리스트 미션의 필수 요구사항을 모두 구현했나요? Gradle test를 실행했을 때, 모든 테스트가 정상적으로 통과했나요? 애플리케이션이 정상적으로 실행되나요? 프롤로그에 셀프 체크를 작성

github.com

 

2단계

https://github.com/woowacourse/java-blackjack/pull/897

[2단계 - 블랙잭 베팅] 밍곰(박민선) 미션 제출합니다. by minSsan · Pull Request #897 · woowacourse/java-black

체크 리스트 미션의 필수 요구사항을 모두 구현했나요? Gradle test를 실행했을 때, 모든 테스트가 정상적으로 통과했나요? 애플리케이션이 정상적으로 실행되나요? 프롤로그에 셀프 체크를 작성

github.com

 


✍🏻 키워드 정리

1. 상속 (with 추상 클래스, 구체 클래스)

이번 미션에서 딜러와 플레이어 사이의 중복 코드를 줄이라는 요구사항이 있었다.

딜러와 플레이어는 블랙잭 게임 참여자로서, 손패를 관리하는 책임을 갖는다.
즉, 두 객체는 손패를 관리하는 로직이 서로 중복된다.

 
손패를 관리하는 로직은 서로 완전히 중복되지만, 일부 룰은 따로 적용된다.

  • 플레이어는 이름을 가지지만, 딜러는 이름을 가지지 않는다.
  • 딜러는 숫자 합이 16이 넘을 때까지 카드를 다시 뽑아야 한다.
  • 처음 카드를 보여줄 때 딜러는 한 장의 카드만, 플레이어는 두 장의 카드를 모두 보여줘야 한다.

이 요구사항을 충족하기 위해 Participant라는 추상 클래스를 만들고, 이를 상속받는 Dealer, Player 클래스를 정의했다.
 
✚ 1단계에서는 추상할만한 부분이 없다고 생각해서 추상 없이 상속을 적용했지만, 2단계에서는 추상 클래스를 적용하여 처음 노출되는 카드 목록을 가져오는 메서드를 추상화했다.
 

구체 클래스 상속에서 추상 클래스로 변경한 과정

 

2. 상속과 합성

앞서 Participant 추상 클래스를 통해 Dealer와 Player를 구현하긴 했지만, 정작 블랙잭 게임 로직에서는 Participant라는 추상 타입을 충분히 활용하지 못하는 것 같다는 생각이 들었다.

  • 현재 참가자가 딜러일 경우에는 딜러의 룰에 따라서, 플레이어일 경우에는 플레이어의 룰에 따라서 게임을 진행해야 함
  • 즉, 게임 진행에 있어서 참가자가 딜러인지 플레이어인지 알아야만 하기 때문

 
'그렇다면, 결국 둘을 Participant로 추상 할 필요가 없다는게 아닐까?' 라는 의문이 생겼다.
특히나 객체지향 관련 서적들에서도 상속을 무조건 배척해야 한다는 듯한 내용이 대다수였다.

상속은 자식이 부모의 구현에 의존하게 만든다. 👉🏻 부모 - 자식 사이에 강한 결합을 만들고, 부모의 캡슐화를 깨뜨린다.

 
상속을 단순히 코드 중복을 줄이기 위해서 사용하면 안된다, 상속 대신 합성을 사용하는 것이 적합하다 등등.. 상속에 관한 부정적인 내용이 가득했다.
 
'상속은 무조건 쓰면 안 되는 건가?' 또 의문이 생겼다.
 
일단 잘 모르겠어서, 책에서 제시한 방법대로 기존에 상속을 적용했던 부분을 합성으로 바꿔봤다.

  • 하지만 둘을 별도의 클래스로 분리하면서 중복 코드가 늘어나고, 참가자 역할 로직이 오히려 분산되는 듯한 느낌이 들었다.
  • 또한, 이번 미션에서는 오히려 상속을 적용했을 때 두 클래스를 Participant 타입으로 묶음으로써 카드와 관련된 로직(덱에서 카드 분배하기 등)을 공통으로 처리할 수 있었다.

 
이 과정에서 블랙잭 미션에서는 합성보다 상속이 가져다주는 이점이 더 크다는 생각이 들었고, 상속을 그대로 유지하게 되었다.
 

이번 미션에서 상속이 적합하다고 느낀 이유가 무엇일까?

사실 여러 이야기를 들어보면 상속은 무조건 안 좋은 것, 쓰면 안 되는 것처럼 보인다.
그럼에도 불구하고, 이번 미션에서 상속을 적용하는게 맞는건지 문득 걱정됐다.
 
하지만 생각해보면, 참가자(Participant)의 룰이 변경되면 당연히 Dealer와 Player도 그에 맞게 변경되어야 한다. 즉, 블랙잭 미션에서는 '참가자와 딜러/플레이어 사이에 강결합이 존재하는 것은 어쩌면 당연한거 아닌가?' 라는 생각이 들었다.
 
그러다가 다른 크루들(새로이, 히포 ...)과 이야기를 나눠보기도 하고, 관련된 내용을 다루는 서적도 일부 참고했다.
이 과정에서 자식이 부모를 대체할 수 있는 존재라면, 상속을 사용해도 괜찮다는 의견을 얻을 수 있었다.
딜러와 플레이어가 참가자에 강하게 결합될 수밖에 없다고 느낀 이유도 이런 맥락이 아니었을까 추측해본다 ..
 

✚ 그리고 상속에 대한 고민은 장기 미션에서도 이어진다. 장기 미션에서는 단순히 중복 코드를 줄이기 위한 상속인지, 역할을 묶기 위한 상속인지 고민을 많이 했었는데 이 내용은 이후 포스팅에서 다뤄보도록!

 

3. 상태에 따라서 로직이 과도하게 분기되는 경우, 상태를 객체로 관리할 수 있다

블랙잭 미션에서는 손패의 상태에 따라서 로직이 분기되는 경우가 많다.

  • 버스트/블랙잭/중단(by 플레이어 요청) 상태에서는 카드를 더이상 뽑을 수 없다.
  • 그 외 손패 합이 21 미만인 경우에는 카드를 더 뽑을 수 있다.
  • 버스트/블랙잭/중단(by 플레이어 요청) 상태에서는 수익을 구할 수 있다.
  • 그 외 상태에서는 수익을 구할 수 없다.

이처럼 상태에 따라서 로직 분기가 과도하게 발생하는 경우, 상태에 따라서 실행되는 로직을 각각의 객체에서 관리하도록 설계를 바꿔볼 수 있다. (상태 패턴이라고 부르는듯?)

객체란, 상태와 그 상태를 필요로하는 행동을 한 곳에서 관리하는 것
즉, 행동과 상태를 객체로 묶어서 관리할 수 있다.

 

상태를 객체로 관리하기 - 적용 예시

 
블랙잭 피드백 강의에서 위 내용을 접하고 직접 적용해봤다.
 
하지만 각 플레이어의 승/패를 결정하는 시점에서는 결국 각 플레이어가 어떤 상태인지(BlackjackState, BustState)를 알아야만 했다.
즉, 게임 진행 과정에서 상태에 따라 나뉘는 로직을 State 객체로 아무리 감싸더라도, State 객체의 외부에서 해당 객체가 어떤 상태인지(= 무슨 클래스인지, = instanceof) 알아야하는 상황이 계속 발생했다.
 
이 과정에서 '블랙잭에서 상태 패턴을 적용하는 이점이 큰가?' 라는 의구심이 들어 결국 적용하지는 않았다. (내가 제대로 적용하지 못한 부분도 분명 있을 것이라 생각한다)
 
 
이후 다른 크루(멍구)와 관련해서 이야기를 나눴는데, 블랙잭 게임 자체가 워낙 규칙이 다양하고 복잡하기 때문에 상태(Blackjack, Bust ...)가 지금보다 훨씬 많아질 수 있다고 한다..!
이러한 경우, 시작 상태에서 종료 상태에 도달하기까지의 과정을 위와 같이 객체로 관리하는 이점이 더 클 수도 있겠다는 생각이 들었다.
그리고 미션을 하면서 분기를 줄이기 위해 객체를 최대한 나누려고 하다보니, 기존 코드에서도 분기가 그렇게 심하지 않았다. 그래서 상태로 묶어서 관리하는 이점을 크게 못 느꼈던 것 같기도 하다...
 
아무튼.. 과도한 분기를 처리하는 방식에 대해서 새로운 관점을 얻어간 것 같다!