Skip to content
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

[3, 4단계 - 체스] 성하(김성훈) 미션 제출합니다. #556

Merged
merged 247 commits into from
Mar 31, 2023

Conversation

sh111-coder
Copy link

@sh111-coder sh111-coder commented Mar 25, 2023

안녕하세요 제이온!! 성하입니다! 😃

저는 크루 중에 Merge가 빨리 된 편인데 3단계 들어가기 전에 여러 가지 리팩토링을 거치는데 시간을 많이 썼습니다!

또, 3단계 4단계도 구현이 어려워서 시간이 엄청 오래 소요된 것 같습니다!

3단계, 4단계에 들어가기 전 리팩토링한 것과 3단계, 4단계에서 궁금한 점들을 질문드리겠습니다!! 🙇🏻‍♂️


1, 2단계 리팩토링

1. 움직이는 방식(ex. MoveStrategy)을 인터페이스로 하는 방식으로 리팩토링 하지 않은 이유

제이온이 해당 방식을 추천해주셨는데, 제 생각이 부족했을 수도 있지만 제가 설계했을 때
이렇게 진행할 경우 RookMoveStrategy 같은 구현체들이 Position을 가져야했습니다!
저는 전략들이 위치를 아는 것은 조금 어색하다고 생각하여 원래 구조를 유지하게 됐습니다!
그래도 생각해볼만한 접근이었던 것 같아요! 감사합니다!! 👍

2. 상태패턴 적용

이전 블랙잭 미션 때 상태 패턴을 처음 들어봤지만, 시간이 없어서 미션에 적용해보지는 못했습니다.

그래서 이번에 Merge가 빨리 되기도 하고, 3단계에서 status 커맨드가 추가되는 것을 보고
반복되는 if문을 줄일 수 있는 상태 패턴을 학습 목적으로 적용해봤습니다!

상태패턴

CommandStatus를 관리하는 CommandManager가 존재하고,
CommandStatus는 다음과 같이 4가지 상태가 존재합니다.

1. Init : 초기 시작 상태(애플리케이션 시작 상태)
2. Play : 'start' 커맨드를 입력하여 게임이 시작된 상태
3. PrintGameResult : 'status' 커맨드를 입력하여 게임 결과를 볼 수 있는 상태
4. End : 'end' 커맨드를 입력하여 게임이 종료된 상태

제가 익숙하지 않아서 그런지 상태 패턴 적용하는 데에만 2~3일을 썼던 것 같아요 ㅠㅠ 😭

제가 상태 패턴을 적용하면서 느낀 장점은 각각 상태별로 동작하는 로직이 다르기 때문에
동작하는 로직에 필요한 것만 필드로 둘 수 있다는 점이었습니다.

컨트롤러에 ChessGame을 두게 되면, 처음 애플리케이션이 실행되는 시점에 보드가 생성되어야 했습니다.
하지만 상태 패턴을 적용해보니 Init 상태에서는 ChessGame을 두지 않아서 애플리케이션 시작 시점에 보드가 생성되지 않게 되었습니다.
Play, PrintGameResult 상태에서만 ChessGame이 필요하여 해당 상태에서만 ChessGame을 들고 있다는 점이 좋았던 것 같아요!

또, 상태별로 관리하다보니 이후의 3, 4단계 구현 시에 상태별로 편하게 DAO를 적용했던 것 같습니다!

제가 느낀 상태 패턴의 단점으로는 상태 하나의 동작이 추가될 때마다
해당 상태의 동작만 추가하면 되는 것이 아니라 모든 상태의 동작을 추가하게 되어 유지 보수가 힘들 것 같았습니다!

제가 상태 패턴을 제대로 적용한건지, 제가 생각한 상태 패턴의 장단점이 적절한지 궁금합니다!!


3, 4단계 구현

1. 3단계

3단계는 ResultCalculator라는 결과 계산 객체를 둬서 진영별 점수진영별 게임 결과를 관리하도록 했습니다!
3단계는 구현이 좀 걸렸지만 질문할 것은 없는 것 같아요!!

2. 4단계

4단계에서는 DB 설계와 DAO 사용이 제일 힘들었던 것 같아요.

제가 설계한 테이블 구조는 다음과 같습니다!
�체스 DB 테이블 설계

piece 테이블에서 chess_game의 game_id를 FK로 가지게 설계하였습니다.
게임이 정상적으로 종료되지 않고 재시작 할 때,
restart라는 커맨드를 추가하여 재시작 할 게임의 ID를 받아서 재시작하게 하였습니다.
동작은 다음과 같습니다!

1. 'restart 10'이 커맨드로 들어온다.
2. chess_game 테이블의 10id진 행이 있으면 게임을 재시작한다.
3. 이때, 게임에 저장되어 있던 모든 기물, 턴 등 체스 게임 정보들을 맵핑하여 구성한다.

DB 설계와 DAO 사용에 관한 질문들이 많은데 코드 단에 코멘트로 남겨두도록 하겠습니다!!
항상 좋은 리뷰 감사합니다 제이온!! 이번 3, 4단계도 잘 부탁드립니다! 🙇🏻‍♂️

이동 기능 매개변수로 Rank,File 별 이동 횟수를 전달받도록 수정
이동 기능 매개변수로 Rank,File 별 이동 횟수를 전달받도록 수정
private JdbcChessGameDao() {
}

public static JdbcChessGameDao getInstance() {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ DAO 싱글톤 질문


DAO에 관해서 구글링 하다가, 싱글톤으로 많이 사용한다는 것을 듣고 적용해봤습니다.

DAO가 사용될 때마다 새로운 인스턴스를 생성하는 것은 낭비이기 때문에 이러한 점에서는 싱글톤이 효과적이라고 생각했습니다!

그런데 현재 DAO를 사용하는 곳은 각 상태들에서 사용하고 있습니다.
이때 싱글톤을 적용하여 static 전역으로 생성하게 되면
다른 객체들도 DAO를 사용할 수 있게 되어 캡슐화 측면에서 좋지 않아보였습니다.

이러한 캡슐화가 깨지는 측면은 상관이 없는 걸까요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

캡슐화의 정확한 의미가 무엇일까요? 또한 static 키워드는 왜 캡슐화를 깨진다고 생각하셨는지 성하의 의견을 코멘트로 적어주세요!
그리고 캡슐화와는 별개로 static를 사용하거나 사용하지 않거나와 관계 없이 다른 객체가 DAO는 사용할 수 있지 않나요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 생각하는 캡슐화는 '객체를 사용하기 전까지는 객체 내부의 정보들을 모르게 하는 것'이라고 생각했어요!

저는 '객체를 사용한다는 것'이 객체의 인스턴스를 만들어서 사용하는 것이라고 이해해서

static은 객체를 사용하지 않고 객체 내부의 정보를 알게 해서 캡슐화가 깨진다고 이해한 것 같아요!

그래서 static을 붙이지 않으면 객체의 인스턴스를 생성해야 DAO를 사용할 수 있기 떄문에

'난 DAO를 사용할 거야!' 라는 행동을 객체의 인스턴스 생성으로 봤습니다.

static으로 선언하면 'DAO를 사용할거야!'라는 행동 없이도 직접 객체 안의 메소드들을 볼 수 있기 때문에 위험하다고 생각했어요!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

캡슐화는 객체의 상태와 행위를 묶고 실제 구현 내용 일부를 외부에 감추어 은닉하는 객체 지향 패러다임의 원칙 중 하나입니다!
위 정의에 따르면 static을 사용하여 객체의 행위에 접근한다고 하더라도 구현 내용을 공개하는 것은 아니기 때문에 static이 캡슐화를 깨뜨리지는 않다고 생각해요 ㅎㅎ 다만 static은 캡슐화와는 별개로 객체의 상태를 사용할 수 없고 다형성과 같은 객체 지향의 장점을 온전히 누릴 수 없고, 메모리에 시작부터 올라가서 해제하기 어렵다는 점에서의 단점이 있습니다.

Copy link

@pjy1368 pjy1368 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 성하!
이번 미션에 DAO와 상태 패턴이라는 난이도 있는 작업을 잘 작업해 주셨네요 👍
코멘트를 작성하다 보니 구조적으로 큰 변화가 있을 것 같아서 일단 이정도만 피드백을 반영해보시면 좋을 것 같습니다 ㅎㅎ
추가로 PR 본문에 남겨주신 질문은 여기에 답변 드릴게요!

Q. 움직이는 방식(ex. MoveStrategy)을 인터페이스로 하는 방식으로 리팩토링 하지 않은 이유
A. 성하가 그렇게 생각하셨다면 그대로 진행하셔도 됩니다 😊

Q. 상태 패턴 적용
A. 말씀 주신대로 상태 별로 고유한 동작만 나눠서 가질 수 있어서 필요한 코드만 둘 수 있다는 장점이 있습니다. 그리고 단점으로는 상태가 늘어날 때마다 기존 상태 코드들의 코드도 변경해야한다고 적어주셨는데, 오히려 상태 패턴으로 상태를 구분해 두었으니 새로운 상태가 추가되면 기존 코드는 거의 영향이 없어야 하는 것 아닐까요? ㅎㅎ

src/main/java/chess/domain/command/CommandManager.java Outdated Show resolved Hide resolved
src/main/java/chess/domain/command/CommandManager.java Outdated Show resolved Hide resolved
src/main/java/chess/domain/command/CommandManager.java Outdated Show resolved Hide resolved
private JdbcChessGameDao() {
}

public static JdbcChessGameDao getInstance() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

캡슐화의 정확한 의미가 무엇일까요? 또한 static 키워드는 왜 캡슐화를 깨진다고 생각하셨는지 성하의 의견을 코멘트로 적어주세요!
그리고 캡슐화와는 별개로 static를 사용하거나 사용하지 않거나와 관계 없이 다른 객체가 DAO는 사용할 수 있지 않나요?

src/main/java/chess/domain/service/dto/ChessGameDto.java Outdated Show resolved Hide resolved
src/main/java/chess/dao/JdbcChessGameDao.java Outdated Show resolved Hide resolved
src/main/java/chess/domain/command/Play.java Outdated Show resolved Hide resolved
@sh111-coder
Copy link
Author

안녕하세요 제이온!! 제이온 덕분에 DAO와 Entity에 대해 더 알게 된 것 같아요! 😃

🎯 제이온의 피드백을 반영한 주요 리팩토링

제이온의 피드백을 반영한 주요 변경점은 다음과 같습니다!

1. DAODTO 대신 Entity를 전달하기 (Entity에서 빌더 패턴 사용)
2. DAO와 도메인을 지는 ChessGameService 서비스 계층 생성
3. ChessGameDao -> 게임 관련 ChessGameDao / 기물 관련 PieceDao로 분리

DAO와 서비스 계층, 상태 패턴에 익숙하지 않다보니까 이렇게만 했는데도 좀 오래 걸렸네요 ㅠㅠ 😭


🎯 리팩토링 진행 시 궁금했던 점

다음은 리팩토링을 진행하면서 궁금했던 점을 질문해보겠습니다!!

✅ 1. Domain인 ChessGame을 상태가 가질지 Service가 가질지 고민

해당 고민을 하느라 엄청 시간을 많이 썼던 것 같습니다.

CommandManager 및 Command 객체들이 DAO 로직도 부르고, Domain 로직도 부르고, Domain & DAO 간의 순서도 맞춰주는 로직도 작성하고, DTO 조립해서 View에 전달하는 로직까지 모두 담당

리팩토링 전에는 제이온이 말해준 것처럼 상태 객체들이 많은 역할을 하게 되었습니다.

ChessGame을 상태가 가진다면, 결국 상태들이 도메인과 서비스를 같이 가져서 위와 같은 역할을 다시 모두 담당하게 되는 것 같았습니다.

그래서 서비스 계층에 Domain인 ChessGame과 DAO를 두게 되었습니다.

서비스 계층이 DAO만 가지고 있는 것이 아니라 Domain을 가져도 괜찮은지 궁금합니다!!


나머지 질문은 코드 관련 질문이라서 코드에 코멘트로 남기겠습니다!! 🙇🏻‍♂️

끝까지 리뷰해주셔서 정말 감사합니다 제이온!! ㅎㅎㅎ 😃

Copy link

@pjy1368 pjy1368 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 성하!
1차 피드백을 전반적으로 잘 반영해주셨네요 👍
Entity 및 Service 분리를 대체적으로 올바르게 사용해주셨고, 이와 관련된 질의응답이나 코멘트를 추가로 남겨두었습니다. 한 번 반영해 보시고 리뷰 요청 주세요!

src/main/java/chess/controller/command/Commands.java Outdated Show resolved Hide resolved
private JdbcChessGameDao() {
}

public static JdbcChessGameDao getInstance() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

캡슐화는 객체의 상태와 행위를 묶고 실제 구현 내용 일부를 외부에 감추어 은닉하는 객체 지향 패러다임의 원칙 중 하나입니다!
위 정의에 따르면 static을 사용하여 객체의 행위에 접근한다고 하더라도 구현 내용을 공개하는 것은 아니기 때문에 static이 캡슐화를 깨뜨리지는 않다고 생각해요 ㅎㅎ 다만 static은 캡슐화와는 별개로 객체의 상태를 사용할 수 없고 다형성과 같은 객체 지향의 장점을 온전히 누릴 수 없고, 메모리에 시작부터 올라가서 해제하기 어렵다는 점에서의 단점이 있습니다.

src/main/java/chess/domain/command/CommandManager.java Outdated Show resolved Hide resolved
Comment on lines +10 to +33
public interface CommandStatus {

CommandStatus start();

CommandStatus restart(Long previousGameId);

CommandStatus move(Position sourcePosition, Position targetPosition);

CommandStatus end();

CommandStatus printGameResult();

boolean isEnd();

boolean canPrintGameResult();

List<Piece> getPieces();

String getTurnDisplayName();

ScoreBySideDto getScoreBySide();

GameResultBySideDto getGameResultBySide();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 Init이나 End 등 CommandStatus 하위 객체들은 위 메소드를 실질적으로 모두 필요로 하지 않습니다.
SOLID 원칙 중 ISP 원칙에 대해 학습해 보시고 적용해보면 어떨까 합니다!

참고할 만한 아티클을 공유드릴게요.

https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-ISP-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%B6%84%EB%A6%AC-%EC%9B%90%EC%B9%99

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 피드백은 선택적이어서 반영안하셔도 무방하지만, 추후 미션을 하시면서 범용 인터페이스보다는 필요에 맞게 분산이 가능할지 고민해보시면 많은 도움이 됩니다! (물론 이건 저도 늘 어렵습니다 ㅋㅋㅋ)

src/main/java/chess/domain/command/CommandManager.java Outdated Show resolved Hide resolved
src/main/java/chess/domain/service/ChessGameService.java Outdated Show resolved Hide resolved
@sh111-coder
Copy link
Author

제이온 안녕하세요! 리뷰이 성하입니다!! 🙇🏻‍♂️

이번에 정말 DM으로 많이 질문을 한 것 같아서 죄송합니다 ㅠㅠ 🥹

그래도 빠르게 답변해주시고 친절하게 해주셔서 정말 감사했습니다... 🥹


🎯 주요 리팩토링

이번 주요 리팩토링은 다음과 같습니다!

✅ 1. Entity 일부 삭제 & 일부 적용

단순히 도메인 getter로 사용할 수 있는 로직들에서는 Entity를 삭제하고,
Entity가 필요한 로직에서는 Service 계층이 아닌 DAO에서 사용하도록 리팩토링했습니다.

gameId로 모든 Piece를 가져오는 findAllPieceByGameId()에서
DB에서 가져온 'piece_type'으로 Piece의 구체 클래스들을 생성하는 로직이 DAO에서 처리하기에는
비용이 컸기 때문에 Entity 내부에서 처리하도록 Entity를 그대로 남겨둬서 적용했습니다!

또, gameId로 ChessGame을 가져오는 findChessGameByGameId 부분에서도
ChessGame을 생성하려면 파라미터로 Board가 필요했는데,
ChessGameDao에서는 Board를 생성할 수 없었습니다.

따라서 chess_game 테이블의 id, turn만 받아와서 Entity를 DAO 단에서 만들고
Service 계층에서 Board를 생성하여 ChessGame을 만들어주도록 했습니다.
이 과정에서 Entity가 왜 필요한지 깨닫게 된 것 같아요!!

제이온이 없었으면 Entity를 생각 못 했을 것 같은데 정말 감사합니다 ㅎㅎㅎ


✅ 2. Init 시 ChessGameService 생성 리팩토링

Init 시에 DAO의 findRecentGameId()를 호출했습니다.
해당 메소드에서 조회된 최근 ID가 없으면 고정 값인 1L을 넣어주고, 있으면 해당 ID를 받아서
ID + 1로 ChessGame을 생성하도록 했습니다!

Copy link

@pjy1368 pjy1368 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 성하!
이번 미션에서 DAO, Entity, Service, Interface, 상속 등 낯설고 어려운 개념을 잘 반영해 주셨다고 생각합니다 👍
DM으로 질의응답한 것 외에 코멘트를 몇 개 달아놨는데, 피드백은 아니고 앞으로 기억해 두면 좋을 메시지만 살짝 남겨놨습니다~
고생하셨습니다. 이만 머지할게요!

Comment on lines +3 to +6
public class ChessGameEntity {

private long id;
private String turn;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앞으로 미션 수행하실 때 이렇게 테이블과 매핑되는 Entity 객체를 만들어두면 다른 개발자가 DB를 직접 까서 테이블을 확인하지않더라도, 테이블 스키마를 확인할 수 있다는 장점도 있습니다 ㅎㅎ

private final ChessGameDao chessGameDao;
private final PieceDao pieceDao;

public static ChessGameService createInitChessGameService() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정적 팩터리 메서드를 만들어주니까 의미 전달이 잘 되네요 👍

public static ChessGameService createInitChessGameService() {
ChessGameDao chessGameDao = JdbcChessGameDao.getInstance();
long recentGameId = chessGameDao.findRecentGameId();
ChessGame chessGame = new ChessGame(++recentGameId, new Board(new Pieces()), Turn.WHITE);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

�레벨 2를 하면서 ID를 조금 더 신경써서 설계해주시면 좋을 것 같습니다! ID는 식별자이니 유일해야 합니다.

Comment on lines +10 to +33
public interface CommandStatus {

CommandStatus start();

CommandStatus restart(Long previousGameId);

CommandStatus move(Position sourcePosition, Position targetPosition);

CommandStatus end();

CommandStatus printGameResult();

boolean isEnd();

boolean canPrintGameResult();

List<Piece> getPieces();

String getTurnDisplayName();

ScoreBySideDto getScoreBySide();

GameResultBySideDto getGameResultBySide();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 피드백은 선택적이어서 반영안하셔도 무방하지만, 추후 미션을 하시면서 범용 인터페이스보다는 필요에 맞게 분산이 가능할지 고민해보시면 많은 도움이 됩니다! (물론 이건 저도 늘 어렵습니다 ㅋㅋㅋ)

@pjy1368 pjy1368 merged commit 5df68aa into woowacourse:sh111-coder Mar 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants