본문 바로가기
Memo/우테코 4기

[우테코] 블랙잭 미션 1단계 학습 로그

by 연로그 2022. 3. 15.
반응형

목차

1. GitHub 저장소

2. 구현 기능 목록

3. 새로운 도전

4. 생각하기

5. 회고

6. 앞으로 할 일


 


 

1. GitHub 저장소

 

Repository: https://github.com/yeon-06/java-blackjack/tree/step1

Pull Request: https://github.com/woowacourse/java-blackjack/pull/220

 

 


2. 구현 기능 목록

 

  • 참여할 사람의 이름 입력
    • 쉼표 기준으로 분리
    • (e) 빈 값, 공백 불가
    • (e) 중복 불가
  • 카드 분배
    • 딜러, 참가자에게 카드 2장씩 분배
    • 딜러의 카드 1장 출력
    • 참가자의 카드 2장 출력
    • 참가자의 카드 합이 21이면 게임 종료
  • 참가자 카드 추가 분배
    • 카드 합이 21 이상인 참가자는 턴 종료
    • 참가자 별로 한장의 카드 추가 여부를 입력 받기 (y/n)
    • 참가자가 카드 추가 여부 입력
      1. y를 선택한 경우 카드 추가 분배
      2. n를 선택한 경우 해당 참가자의 턴 종료
    • 참가자의 카드 목록 출력
  • 딜러 카드 추가 분배
    • 16 이하면 1장 추가 분배
    • 17 이상이면 턴 종료
  • 결과 출력
    • 카드 목록 및 총합 출력 (ex: 딜러 카드: 3다이아몬드, 9클로버, 8다이아몬드 - 결과: 20)
    • 최종 승패 계산
    • 최종 승패 출력
      1. 딜러 n승 n패 출력
      2. 참가자 승/패 결과만 출력

 


3. 새로운 도전

 

3-1. Abstract Class

 연료 주입에 대해서 Abstract Class와 Interface에 대해 고민하다 Abstract Class를 도입했다. Abstract Class는 흔히 상위 클래스의 변동이 하위 클래스까지 영향을 끼칠 수 있기 때문에 꺼린다. K5, Sonata, Avante는 Car에 종속된다는 현재 경우에는 오히려 그 영향이 필요하다고 생각 했다. Car가 변동됨으로써 하위 클래스에 이슈가 생긴다면 그건 추상 클래스를 도입했기 때문이 아니라 Car의 변동 내역이 잘못된 거라는 생각이 든다.

 

➕ 추가 공부: 상속 vs 조합 https://yeonyeon.tistory.com/206

 

[Java] Inheritance(상속) vs Composition(조합)

목차 상속; Inheritance 조합; Composition 질문 사항 상속보다는 컴포지션을 써야한다? 상속 설계 시 hook을 주의하자. 상속용 클래스의 생성자에서 재정의 가능한 메서드 호출 금지 Cloneable과 Serializable

yeonyeon.tistory.com

 

HashMap은 왜 extends도 받고 implements도 받을까?

👉 AbstractMap은 Map interface를 구현한다.

HashMap은 이 AbstractMap을 상속 받아서 사용하는데 Map interface를 굳이 또 implements했다.

 

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable { ... }
    
public abstract class AbstractMap<K,V> implements Map<K,V> { ... }

 

왜 굳이 둘 다 implements한거지? implements를 함으로써 얻는 이득이 있을까? 하면서 찾아봤는데 Java 1.2에서 HashMap이 처음 생겨났을 때부터 내려온 전통 같은거라고 한다... 자바 개발자에게 물어보니 굳이 이런 식으로 구현했던건 실수..였다고 대답했다고 한다😭 (자료 찾아주신 제이슨 감사합니다ㅠㅠ)

 

관련 자료

 

 


4. 생각하기

🙋‍♀️: 연로그(본인), 🙍‍♂️: 로운(리뷰어), 👨‍⚖️: 구구(리뷰어)

 

 

4-1. 공통으로 사용하는 테스트 메소드

 

🙋‍♀️ 편의를 위해 Cards를 생성해주는 getCards() 메소드를 생성했습니다. 요 메소드를 테스트 코드 여기저기서 사용해서 사용하는 곳마다 생성해주었습니다. 원래는 public으로 만들었는데 혹시나 메소드 내부 로직이 변경되면 다른 테스트 코드에 영향을 줄 수 있다고 생각했습니다. 서로 다른 패키지에 존재하는 경우 테스트 메소드를 public화 해서 사용하는건 어떻게 생각하시나요?

 

🙍‍♂️ 서로 다른 패키지에 존재하더라도 같은 객체를 필요로하는 테스트들이 많아요. 그래서 매번 그 객체들을 생성하기보단 한곳에서 생성해서 반환해주는 메서드를 만들고, 그 메서드를 공통으로 쓰는 경우가 많습니다ㅎㅎ 제 개인적으로는 TestUtil이라는 형태로 만들어서 사용하고 있어요~

 

👉 TestUtil 생성하여 메소드 분리

 

4-2. Abstract Class vs Interface

 

🙋‍♀️ 

  • Abstract Class: 상속을 이용해 메소드를 재사용하고 추가적인 기능을 확장하는데 용이
  • Interface: 공통적인 메소드를 묶어 해당 메소드들을 구현하도록 명시적으로 나타내줌

의 정도로 인식하고 있는데 본 프로젝트에서는 abstract class가 적합하다고 생각되어 사용했습니다.
헌데 왜 쓰지 말라! 는 식의 강조가 많은지는 이해가 잘 안돼요. 실무에서 쓰는 경우가 드문가요 ?_?

 

🙍‍♂️ 제 개인적인 의견입니다~

저는 추상클래스를 쓰지 말라는 주의가 아닌데요. 어떤 기능이나 기술도 필요하면 쓰고 정의에 맞게 사용하면 된다고 생각합니다. 추상클래스를 쓰지 말라고 하는 이유들은 제가 생각했을 때 추상클래스를 용도에 맞게 잘 사용하지 않는 사람들이 많기 때문이라고 생각해요. 제가 생각하는 추상클래스는 하위클래스와 정확하게 일치할 때 쓴다고 생각합니다.


 추상클래스 A와 이를 상속받는 하위클래스 B는 정확하게 의미와 역할이 일치할 텐데요. 여기서 문제는 B에 점점 메서드나 내용, 역할이 추가될 때 문제가 발생하는 것 같아요. A와 B가 같지 않은데 단순히 중복을 없애는 용도로만 사용하게 될 경우, 복잡성이 높아지고 객체지향에 맞지 않는 결과가 나오게 되는 것 같아요. B의 역할이 커지게 되면 이 때 A와 B가 같은지를 고려해야하고 필요하면 분리하여 추상클래스로 묶지 말아야하는데 이러한 부분들이 안지켜지기 때문에 추상클래스를 사용하지 말라는 말이 나오는 것 같습니다~

 

 제 개인적인 경험을 말씀드리면 추상클래스에 코드가 굉장히 많은 경우가 있었어요. 이때 필요한 부분만 abstract 메서드로 빼서 각각에서 구현을 하게한 것이 있었는데요. 이 부분이 로직의 흐름을 파악하기 힘들게 만드는 부분이 되더라고요. 구현체쪽으로 가서 로직 확인했다가 다시 추상클래스로 돌아와서 나머지 로직 확인하고 다시 구현쪽으로 넘어가는 형식으로 파악을 하는데 상속체가 많고, 추상클래스의 코드가 큰 경우에는 파악하기 힘들었던 경험이 있습니다~

 

4-3. 연료 주입 미션에서 interface 사용

🙋‍♀️ 블랙잭 미션은 아니지만 렌트카에서 interface 사용하라는 조건이 있었습니다. interface를 사용해서 구현해보았으나 abstract class보다 이점이라고 느껴지는 점이 없어서 롤백했습니다. 혹시 어떤 맥락에서 interface를 사용하려고 한 것인지 힌트를 주실 수 있을까요?ㅜ.ㅜ

 

🙍‍♂️ 그러면 연로그가 생각하는 abstract class의 이점은 무엇이었는지 알 수 있을까요?? 그래야 이야기를 해볼 수 있을거 같아요ㅎㅎ

 

🙋‍♀️ Abstract Class는 흔히 상위 클래스의 변동이 하위 클래스까지 영향을 끼칠 수 있기 때문에 꺼린다고 알고 있는데요. K5, Sonata, Avante는 Car에 종속되는 현재 경우에는 오히려 그 영향이 필요하다고 생각 했습니다. Car가 변동됨으로써 하위 클래스에 이슈가 생긴다면 그건 추상 클래스를 도입했기 때문이 아니라 Car의 변동 내역이 잘못된 거라는 생각이 듭니다.

 

4-4. dto의 사용

과제를 위 방식처럼 구현하진 않았지만.. 아무튼 계층간 이동엔 DTO!

 

🙋‍♀️ 이전 미션에서는 생성자, getter에서 불변 처리를 해줘도 충분했는데 이번에는 구조가 복잡해지면서 불변 처리를 하는게 헷갈리더라고요 불변 처리 역할을 위임하기 위해 dto를 생성하는 것은 어떻게 생각하시나요?

 

🙍‍♂️ 새로운 객체로 감싸서 반환을 할 수도 있고 불변으로 반환을 할 수도 있었을텐데 getter에서 불변으로 반환한 이유가 무엇인가요?? 이 이유가 dto를 사용하는 이유와 일맥상통 한다면 사용해도 좋다고 생각합니다! dto를 사용하는 이유는 무엇일까요??

 

🙋‍♀️ View에 도메인 모델이 아닌 dto를 전달하면 뷰가 변경되어도 부담 없이 필드를 변경할 수 있고 뷰에서 이용하는 로직을 dto에게 책임을 위임하는 등의 역할을 할 수 있기 때문에 사용합니다! 또한 dto를 통해 전달할 경우 뷰에 데이터를 전달하기 전에 도메인 값이 변경되어도 원래 보내려던 값을 안전하게 보낼 수 있습니다. 이 부분의 연장선으로 dto에서 불변 처리를 해주려고 했는데 지금 다시 생각해보니까 dto를 생성해줄때마다 불변 처리 로직을 추가할 바에는 안전하게 도메인에서 처리해주는게 맞다는 생각이 드네요😂

 

🙍‍♂️ dto에서 불변처리 로직은 저도 필요가 없다고 생각하고요. 단순히 필요한 데이터만 잘 넘겨주면 된다고 생각합니다!
단 나중에 dto를 쓸 때 생각해야하는 점은 view에서 이용하는 로직을 dto에 위임한다고 했는데요. dto에 로직이 들어가도 될까요? 실제 웹이나 앱에서 어떻게 보여줄지 서버에서 고려를 할까요?? 어떠한 형태로 어떻게 보여줄지는 누구의 역할일까요? 이러한 관점에서 보면 dto에 어떤 정보가 들어가야하는지 판단할 수 있지 않을까요??

 

4-5. Controller의 역할이란?

콘솔 화면

 

👨‍⚖️ 클래스명에서 컨트롤러를 제거해봅시다.

 

🙋‍♀️ MVC를 도입하다보니 편의상 Controller라고 이름 붙였는데 실제로 Controller의 기능을 하진 않아요. 현재 미션들은 콘솔에서 동작하다보니 View의 역할과 함께하기 때문에 이름에 대한 제안을 해주신 것 같은데 맞을까요?

 

👨‍⚖️ 그것도 있지만 MVC라는 틀을 고집할 필요가 있을까요? 콘솔에서 동작하는거니 ConsoleGame 같은게 더 적합한 이름이 아닐지 생각해봅시다.

 

 


5. 회고

 

 전체적으로 컨디션 관리를 못했던 주였다. 한번 게더에서 심도 있는 토론을 구경하다가... 4시에 자버렸는데 그 후로 수면 패턴이 꼬였다. (하지만 그 순간으로 돌아간다면 같은 선택을 할 것이다😇 재미있었다..ㅎ) 그 영향인지 컨디션이 저조한 편이었고 머리가 잘 안돌아가서 새로운 도전을 별로 못해본 것 같다.

 

 머지 됐음에도 아쉬움이 많이 남는다😭 운동을 시작하려고 하는데 운동 하고 기절잠 한 번 자면 수면 패턴 되찾을 수 있지 않을까? 수면 패턴 되찾고... 리뷰어님 엄청 귀찮게 해야지... step2 부터는 다시 힘내자!

 

 

객체지향 세계에서는 모든 것이 능독적이고 자율적인 존재로 바뀐다.
레베카 워프스브록은 능동적이고 자율적인 존재로 소프트웨어 객체를 설계하는 원칙을 가리켜 의인화(anthropomorphism)라고 한다.
훌룡한 객체지향 설계란 소프트웨어를 구성하는 모든 객체들이 자율적으로 행동하는 설계를 가리킨다.

 

 


6. 앞으로 할 일

 

  • 각 도메인이 가져야할 책임에 대해 글로 정리해보기
  • dto의 역할을 생각해보고 적용해보기
  • 불변 처리 확인하기
  • CardDistributor - static 블록에서 초기화하지 않고 Generator 생성하기
  • Participant - 생성자에서 Cards를 받을 필요가 있는가?
  • Cards - 상태에 관한 로직 Status로 이동
  • Cards - 전체 합계 구할 때 ACE의 경우 10을 더하는건 어느 책임일까?
  • Participants - drawCard()에서 class를 확인하는 부분에 대해 다시 생각해보기
반응형