[1,2 단계 - 체스] 제나(위예나) 미션 제출합니다.#486
Conversation
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
jnsorn
left a comment
There was a problem hiding this comment.
안녕하세요 제나! 리뷰가 늦었습니다. 🥲
전반적으로 아주 잘 구현해주신 것 같다는 생각이 드네요.
이전에 학습한 내용도 적절하게 잘 적용해주신 것 같아요.
몇가지 코멘트 남겼는데 확인해보시고, 편하게 DM이나 코멘트 남겨주세요.
이번주도 화이팅입니다. ♟️
| private void start(final ChessGame chessGame) { | ||
| Status gameStatus = new Start(chessGame); | ||
| while (gameStatus.isRun()) { | ||
| List<String> commands = InputView.getCommand(); | ||
| final Command command = Command.findCommand(commands); | ||
| gameStatus = gameStatus.checkCommand(command); | ||
| OutputView.printBoard(chessGame.getChessBoard()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Controller에서 최대한 로직을 덜어냈네요! 특히, 상태에 따라 행위가 달라지는 부분도 별도 객체에 책임을 위임하니 Controller가 최소한의 행위만 하고 있는 것 같아요. 💯
| private static char getPieceName(final Map<Position, Piece> board, final int rank, final int file) { | ||
| final Position position = new Position(rank, file); | ||
| if (board.containsKey(position)) { | ||
| return PieceName.findMessage(board.get(position)); | ||
| } | ||
| return BLANK; | ||
| } |
There was a problem hiding this comment.
view에서 출력을 위해 Position 객체를 생성하고 있네요!
view와 model간의 의존성을 제거해보면 어떨까요?
There was a problem hiding this comment.
view 와 domain 간의 의존성을 제거하기 위해서 ChessBoardDTO 를 만들어서 String 으로 보드 정보를 변환해 출력하는 것으로 변경했습니다.
근데 rank 와 file 순서대로 반복문 돌리면서 Position 에 기물이 있는지 확인하고 있으면 symbol을, 없으면 empty 를 반환하는 로직에서 계속해서 Position 이 생성되어서 계속 객체가 생성되는 것을 방지하기 위해 Position 을 캐싱하는 방법을 적용했습니다.. 다른 방법이 감이 안왔고 😥Position 이 체스판 수만큼 64개 캐싱해두면 불필요하게 객체 생성이 되는 걸 방지할 수 있어서 해당 방법을 적용해봤는데 혹시 더 좋은 방법이 있을까요ㅜㅜ ?? Map 이 순서가 있게 저장되면 좋을텐데 순서가 없어서 Position 순서대로 정렬할 수 가 없어서 방법을 찾기가 힘들었습니다 ㅠㅠ
There was a problem hiding this comment.
ChessBoardDto에서는 Position을 꼭 객체로 가지고 있지 않아도 될 것 같아요. DTO 생성시에는 Map<Position, Piece>을 받더라도, DTO 내부에서는 Map<String, String>과 같은 형태로 표현할 수 있지 않을까요?
|
|
||
| public static Command findCommand(final List<String> commands) { | ||
| final CommandType type = Arrays.stream(CommandType.values()) | ||
| .filter(e -> e.name().equalsIgnoreCase(commands.get(0))) |
There was a problem hiding this comment.
명령어 리스트 중 첫번째 명령어(start, move, end)만 뽑아서 검증하기 위한 의미였습니다! 매직넘버 상수화 했습니다.
| public enum CampType { | ||
| BLACK, | ||
| WHITE; | ||
|
|
There was a problem hiding this comment.
체스에서 각 플레이어의 팀을 뜻하는 용어가 없어보여서 '진영' 이라는 뜻의 Camp 를 사용했습니다!
다시 보니 TeamColor 로 정의하는게 더 직관적으로 이해하기 좋을 거 같아 이름 변경했습니다 😀
| } | ||
|
|
||
| private Status move(final Command command) { | ||
| validateCommand(command); |
There was a problem hiding this comment.
move에서 입력값 검증과 move 행위 두 가지를 하고 있는 것 같아요.
검증은 checkCommand에서 하는게 더 적절해보이는데 제나 생각은 어떠세요?
There was a problem hiding this comment.
또링 말씀대로 checkCommand 에서 검증해까지 해주고 move 에서는 move 행위만 해주는게 더 적절하다고 생각해 메소드 위치 이동했습니다 !!
| public boolean contains(final Position possiblePosition) { | ||
| return board.containsKey(possiblePosition); | ||
| } | ||
|
|
||
| public Piece checkPiece(final Position source) { | ||
| if (contains(source)) { | ||
| return board.get(source); | ||
| } | ||
| throw new IllegalArgumentException("체스말이 존재하는 위치를 입력해 주세요."); | ||
| } |
There was a problem hiding this comment.
사소한 네이밍 관련 의견인데요,
- contains 메서드 입장에선 매개변수가 possiblePosition인지 몰라도 될 것 같아요. position 정도로 간단하게 표현하는건 어떨까요?
- piece를 반환하고 있으니 check보단 get이나 find가 어울릴 것 같아요.
| /** | ||
| * 폰의 이동 규칙을 관리한다. | ||
| * 1) 첫 시도 - 대각선 위치에 다른 편의 기물 있음 : 공격 | ||
| * 2) 첫 시도 - 대각선 위치에 기물 없음 : 최대 2칸까지 이동 가능, 가능한 위치면 이동 | ||
| * 3) 그 외 - 대각선 위치에 기물 있음 : 공격 | ||
| * 4) 그 외 - 대각선 위치에 기물 없음 : 앞으로 1칸 이동 | ||
| * | ||
| * @param source 시작 위치 | ||
| * @param target 종료 위치 | ||
| * @param piece 체스 말 | ||
| * TODO : 여기서 책임을 가지는 건 무의미한 것 같은데, 어떻게 분리하지? | ||
| */ |
There was a problem hiding this comment.
TODO에도 적어주신것처럼, pawn의 공격 및 이동 규칙이 ChessGame에 있는데 더 적절한 객체로 옮겨보면 어떨까요? 혹시 고민되는 부분이 있다면, DM 남겨주세요!
There was a problem hiding this comment.
Pawn 을 BlackPawn 과 WhitePawn 으로 분리하고
Piece 의 canMove 에 매개변수로 targetPiece 를 추가해서 Pawn 에서 이동 및 공격 가능 여부를 판단할 수 있게끔 리팩토링 했습니다.
다른 기물들은 canMove 메소드 사용 시에 targetPiece 가 필요가 없는데 Pawn 때문에 매개변수 하나가 추가되었습니다..🥲 Piece 가 Position 을 가지고 있지 않게 설계했고 Piece 에 chessBoard 정보를 넘기고 싶지 않아서 Piece 에서는 가능한 경로에 target 이 포함되어있는지 확인하는 책임만 주었고 chessBoard 에서 target까지 가는 경로에 기물이 있는지 판단하는 책임을 주어서 (isPossibleRoute 메소드) 이 방식이 그나마 제일 코드가 깔끔해져서 이렇게 수정했습니다..
There was a problem hiding this comment.
이미 Piece가 Team을 가지고 있는데, Pawn을 BlackPawn과 WhitePawn으로 분리할 필요가 있을까요?
하나의 Pawn으로도 표현할 수 있을 것 같아요!
There was a problem hiding this comment.
BlackPawn 과 WhitePawn 은 움직이는 방향이 달라서 이동 가능한 위치를 계산하는 Direction 이 각각 DOWN, UP 으로 다르고 공격할 수 있는 방향도 각각 아래 대각선, 위 대각선으로 달라서 Pawn 하나로 관리할 시에는 canMove 와 canAttack 메소드에서 if 문으로 분기처리가 필요해서 분리를 하였습니다.!
1,2,3 단계까지는 분리해서 구현하는게 더 코드가 깔끔해보였는데.. 4단계 때 DB 에서 select 로 piecetype 을 받아와서 Piece 를 생성하는 부분에서
public static Piece toPiece(final PieceType pieceType, final TeamColor color) {
if (pieceType == PAWN) {
return getPawn(color);
}
return Arrays.stream(values())
.filter(it -> it.name().equalsIgnoreCase(pieceType.name()))
.map(it -> it.toPiece.apply(pieceType, color))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("기물을 찾을 수 없습니다."));
}
private static Pawn getPawn(TeamColor color) {
if (color == TeamColor.BLACK) {
return new BlackPawn(PieceType.PAWN);
}
return new WhitePawn(PieceType.PAWN);
}
이렇게 분기처리가 필요해져서 다시 하나로 합칠 필요성도 보입니다 😥
|
|
||
| import java.util.List; | ||
|
|
||
| public enum Direction { |
| public abstract class Piece { | ||
| private final PieceType pieceType; | ||
| private final CampType campType; |
There was a problem hiding this comment.
Piece가 Movable을 가지고 있을꺼라 생각했는데, 조금 다르게 구현하셨군요!
Piece가 Movable을 갖지 않는 이유가 궁금한데, 얘기해주실수 있나요~?
There was a problem hiding this comment.
Piece 와 PieceMove 들을 각각 TDD 로 만들면서 합치는 과정에서 추상화 계층이 불필요하게 나눠진듯합니다 사실 급하게 구현해서 기억이 잘 안납니다 😿
Movable 인터페이스를 Piece 에서 구현하는 식으로 변경했습니다!! (canMove 메소드 오버라이드)
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| public final class Move { |
There was a problem hiding this comment.
Move 에서는 piece 가 갈 수 있는 Position 들의 Set 을 반환하는 역할을 하고 있었는데 네이밍이 역할에 맞지 않다는 것을 확인해서 PossibleDestination 으로 일급 컬렉션 객체로 수정했습니다 !
Piece 가 바로 Movable interface 를 구현해 canMove 메소드를 오버라이딩하도록 수정
|
안녕하세요 또링! 리뷰 반영이 늦었습니다ㅜㅜ 리뷰 감사합니다 🙇♀️ |
jnsorn
left a comment
There was a problem hiding this comment.
안녕하세요, 제나! 피드백 잘 반영해주셨네요 👍
코멘트도 잘 남겨주셔서 의도 파악하기에도 수월했습니다.
추가로 코멘트 남겼는데, 3,4단계 진행하면서 같이 반영해주시면 좋을 것 같아요!
남은 오후도 (..♟️와 함께.....) 잘 보내시고, 다음 단계에서 만나요 👋
| movePiece(source, target, piece); | ||
| } | ||
|
|
||
| private void validateCamp(final Piece piece) { |
| /** | ||
| * 폰의 이동 규칙을 관리한다. | ||
| * 1) 첫 시도 - 대각선 위치에 다른 편의 기물 있음 : 공격 | ||
| * 2) 첫 시도 - 대각선 위치에 기물 없음 : 최대 2칸까지 이동 가능, 가능한 위치면 이동 | ||
| * 3) 그 외 - 대각선 위치에 기물 있음 : 공격 | ||
| * 4) 그 외 - 대각선 위치에 기물 없음 : 앞으로 1칸 이동 | ||
| * | ||
| * @param source 시작 위치 | ||
| * @param target 종료 위치 | ||
| * @param piece 체스 말 | ||
| * TODO : 여기서 책임을 가지는 건 무의미한 것 같은데, 어떻게 분리하지? | ||
| */ |
There was a problem hiding this comment.
이미 Piece가 Team을 가지고 있는데, Pawn을 BlackPawn과 WhitePawn으로 분리할 필요가 있을까요?
하나의 Pawn으로도 표현할 수 있을 것 같아요!
| static ChessBoardFactory getInstance(final ChessGame chessGame) { | ||
| if (CACHE.containsKey(chessGame)) { | ||
| return CACHE.get(chessGame); | ||
| } | ||
| final ChessBoardFactory chessBoardFactory = new ChessBoardFactory(); | ||
| CACHE.put(chessGame, chessBoardFactory); | ||
| return chessBoardFactory; | ||
| } |
There was a problem hiding this comment.
엇 제가 위에 코멘트를 살짝 잘못 단 것 같네요. 😭현재 ChessBoardFactory를 캐싱하여 사용하고 있는데 왜 캐싱을 사용하는지 이유가 궁금했어요! Factory가 캐시로 관리되어야 하는 이유가 있을까요~?
| private static char getPieceName(final Map<Position, Piece> board, final int rank, final int file) { | ||
| final Position position = new Position(rank, file); | ||
| if (board.containsKey(position)) { | ||
| return PieceName.findMessage(board.get(position)); | ||
| } | ||
| return BLANK; | ||
| } |
There was a problem hiding this comment.
ChessBoardDto에서는 Position을 꼭 객체로 가지고 있지 않아도 될 것 같아요. DTO 생성시에는 Map<Position, Piece>을 받더라도, DTO 내부에서는 Map<String, String>과 같은 형태로 표현할 수 있지 않을까요?
| public boolean isUpsideDiagonalPosition(Position target) { | ||
| int fileGap = target.file - this.file; | ||
| int rankGap = target.rank - this.rank; | ||
| return Math.abs(fileGap) == 1 && rankGap == 1; | ||
| } | ||
|
|
||
| public boolean isDownSideDiagonalPosition(Position target) { | ||
| int fileGap = target.file - this.file; | ||
| int rankGap = target.rank - this.rank; | ||
| return Math.abs(fileGap) == 1 && rankGap == -1; | ||
|
|
||
| } | ||
|
|
There was a problem hiding this comment.
해당 로직을 Direction 객체를 이용해 판단하는 것으로 수정했습니다 !
안녕하세요 또링 ! 😊
체스 미션을 구현해서 리뷰 요청 드려요 !!
에러 발생 시 재입력 기능이 구현되지 않아서 게임 실행 스크립트를 작성해봤습니다 .. 😿
리뷰 감사드립니다 🙇♀️