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

[1, 2단계 - 체스] 져니(이지원) 미션 제출합니다. #485

Merged
merged 102 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from 88 commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
5274392
docs: 기능 명세서를 작성한다
Cl8D Mar 14, 2023
03ee6d5
feat(Position) : 체스판 목표 위치 반환 기능을 구현한다
Cl8D Mar 14, 2023
21c2ca1
feat(CampType): 입력받은 위치에 따라 진영을 나누는 기능을 추가한다
Cl8D Mar 14, 2023
0d3d52b
feat(Camp): 입력받은 위치 정보를 검증하고, 진영을 나눈다
Cl8D Mar 14, 2023
70d1806
feat: 체스판 목표 위치 반환 기능을 구현한다
Cl8D Mar 14, 2023
b74cd59
feat: 입력받은 위치에 따라 진영을 나누는 기능을 추가한다
Cl8D Mar 14, 2023
dc81448
feat: 입력받은 위치 정보를 검증하고, 진영을 나눈다
Cl8D Mar 14, 2023
1a837b3
feat: 진영을 검은색과 흰색으로 분리한다
Cl8D Mar 15, 2023
71a8a9b
feat: 체스말의 종류를 관리한다
Cl8D Mar 15, 2023
248d295
feat: 체스판을 초기화한다
Cl8D Mar 15, 2023
03c9a07
refactor: 도메인별로 패키지를 분리한다
Cl8D Mar 15, 2023
8c8c198
feat: 체스판 초기화 시 체스말에 대한 정보만 저장한다
Cl8D Mar 15, 2023
d340443
feat: 사용자가 입력한 명령어를 검증한다
Cl8D Mar 15, 2023
a55dd78
feat: 체스말이 흰색 진영인지 확인한다
Cl8D Mar 15, 2023
f073c49
feat: 게임을 시작하면 체스판을 초기화한다
Cl8D Mar 15, 2023
fdff963
feat: 입력받은 위치를 정수값으로 변환한다
Cl8D Mar 15, 2023
af16004
feat: 특정 위치에 체스말이 존재하는지 확인한다
Cl8D Mar 15, 2023
6ae4743
feat: 특정 위치에 존재하는 체스말을 반환한다
Cl8D Mar 15, 2023
0d03670
feat: 퀸이 이동할 수 있는 모든 경로를 확인하고, 이동 가능한지 판단한다
Cl8D Mar 15, 2023
1b8fd8c
feat: 가능 방향에 체스말이 존재하는지 검증하는 기능을 Move의 책임으로 변경한다
Cl8D Mar 15, 2023
4472050
feat: 움직임을 표현하는 인터페이스를 추가한다
Cl8D Mar 15, 2023
2ea85ee
feat: 룩이 이동할 수 있는지 판단한다
Cl8D Mar 15, 2023
aecdcf9
test: CampType의 수정에 따라 테스트를 변경한다
Cl8D Mar 15, 2023
a7d2f08
test: 퀸, 룩의 테스트 조건을 수정한다
Cl8D Mar 15, 2023
0d8d1c0
feat: 비숍이 이동할 수 있는지 판단한다
Cl8D Mar 15, 2023
2063c36
refactor: 각 체스말들이 최대 이동 거리를 상수화한다
Cl8D Mar 15, 2023
62f5fb5
feat: 킹이 이동할 수 있는지 판단한다
Cl8D Mar 15, 2023
26a7030
feat: 나이트가 이동할 수 있는지 판단한다
Cl8D Mar 15, 2023
0bbac7f
feat: 폰이 이동할 수 있는지 판단한다
Cl8D Mar 16, 2023
f2d3668
feat: 입력받은 위치에 대한 검증을 PositionConverter의 역할로 위임한다
Cl8D Mar 16, 2023
ed9ac2b
refactor: 명령어를 입력받는 메서드의 네이밍을 변경한다
Cl8D Mar 16, 2023
fd4667c
feat: 예외가 발생하면 재시도하는 기능을 제거한다
Cl8D Mar 16, 2023
144ce4e
feat: 플레이할 진영을 번갈아가며 반환한다
Cl8D Mar 16, 2023
7f0ebd0
feat: 위치가 입력받은 제한값을 넘어가는지 판단한다.
Cl8D Mar 16, 2023
ed54a8c
feat: 목표 위치로 이동하기 위한 단위 벡터를 계산한다
Cl8D Mar 16, 2023
c07d5eb
feat: 특정 위치에 존재하는 체스말을 제거한다
Cl8D Mar 16, 2023
1f137d1
feat: 입력받은 위치에 체스말을 둔다
Cl8D Mar 16, 2023
0f01ec7
feat: 특정 체스말이 시작 위치에서 종료 위치까지 이동 가능한지 판단한다
Cl8D Mar 16, 2023
22beb9e
feat: 입력받은 위치를 복사한 새로운 위치를 반환한다
Cl8D Mar 16, 2023
d556de0
feat: 특정 말의 종류에 따라서 시작 위치에서 종료 위치로 이동 가능한지 판단한다
Cl8D Mar 16, 2023
f2bb1c1
test: rank와 file의 위치를 수정한다
Cl8D Mar 16, 2023
edb495e
feat: 체스말의 진영과 종류를 판단하는 기능을 추가한다
Cl8D Mar 16, 2023
be1433f
feat: 현재 위치의 rank가 입력받은 rank보다 큰지 판단한다.
Cl8D Mar 16, 2023
f07c41d
feat: 첫 턴에 WHITE 진영의 폰과 나이트만 움직이는지 검증한다.
Cl8D Mar 16, 2023
4e4f5b0
feat: 체스말이 입력받은 진영에 속하는지 판단한다
Cl8D Mar 16, 2023
689228d
feat: 체스말이 시작 위치에서 도착 위치로 이동 가능한지 판단한다
Cl8D Mar 16, 2023
547e146
feat: 각 말에 대한 객체를 분리한다
Cl8D Mar 16, 2023
27ca651
test: 각 말이 이동 가능한지 테스트한다
Cl8D Mar 16, 2023
045aca6
refactor: 이동 가능한지 판단하는 메서드에 대해 분리한다
Cl8D Mar 16, 2023
7605501
Merge remote-tracking branch 'origin/step1' into step1
Cl8D Mar 16, 2023
77a5101
refactor: chessGame에서 체스판을 만드는 로직을 수정한다
Cl8D Mar 16, 2023
19f8ede
feat: 체스판의 생성 로직을 변경한다
Cl8D Mar 16, 2023
7bcec7b
feat: 체스말의 움직임 전략을 static하게 관리한다
Cl8D Mar 16, 2023
11ce918
refactor: 불필요한 파일을 제거한다
Cl8D Mar 16, 2023
45b6bf4
feat: 사용자가 입력한 명령어에 대한 관리 방식을 변경한다
Cl8D Mar 16, 2023
7578073
feat: 게임의 상태를 start, end, move로 관리한다
Cl8D Mar 16, 2023
3250f4f
feat: 체스 게임의 시작 기능을 추가하고, view를 static하게 관리한다
Cl8D Mar 16, 2023
b7c8649
test: 테스트 케이스에 대한 오류를 제거한다
Cl8D Mar 17, 2023
c7b3c4c
test: 게임의 진행 상태에 대해 테스트 코드를 작성한다
Cl8D Mar 17, 2023
1e46715
feat: 나이트의 움직임 조건에 대해서 변경한다
Cl8D Mar 17, 2023
ee5169c
feat: 체스 게임을 진행하는 기능을 추가한다
Cl8D Mar 17, 2023
f8876f1
feat: 예외 발생 시 재입력을 받도록 수정한다
Cl8D Mar 17, 2023
921b7ee
feat: 체스말이 이동 가능한 위치에 대해 구하는 로직을 수정한다
Cl8D Mar 17, 2023
dc5b490
feat: 체스말의 공격 가능 여부와 시작 지점, 종료 지점까지의 경로가 가능한지 판단하는 기능을 추가한다
Cl8D Mar 17, 2023
d4f94a0
feat: 각각의 체스말에 대해서 위임하는 메서드를 추가한다
Cl8D Mar 17, 2023
95a7c9e
test: 폰의 이동을 테스트하는 경우를 수정한다
Cl8D Mar 17, 2023
5e405a6
feat: 체스 게임을 진행하는 전반적인 로직을 수정한다
Cl8D Mar 17, 2023
334312a
feat: 이동 가능한 위치에 대해서 Location 클래스로 관리한다
Cl8D Mar 17, 2023
48511d1
refactor: 체스 게임의 실행 메서드의 접근 제어자를 private로 변경한다
Cl8D Mar 17, 2023
062a2d4
refactor: 패키지에 대한 이동을 진행한다
Cl8D Mar 17, 2023
e19dabf
refactor: 상수에 대한 매직 넘버 처리를 진행한다
Cl8D Mar 17, 2023
bc1d707
feat: position에 대한 getter를 제거한다
Cl8D Mar 17, 2023
320e7cf
feat: 종료 시에는 체스판이 출력되지 않도록 수정한다
Cl8D Mar 17, 2023
6872471
test: 불필요한 로깅을 제거한다
Cl8D Mar 20, 2023
4fcc154
test: 기존의 진영에서 다른 진영으로 턴을 변경하는 기능을 테스트한다
Cl8D Mar 20, 2023
50dcdb3
feat: PositionConverter의 생성자를 private로 막는다
Cl8D Mar 20, 2023
972fb93
test: 각 체스말이 움직일 수 있는 위치를 반환하는 기능을 테스트한다
Cl8D Mar 20, 2023
e3e4fab
refactor: Moveable 인터페이스와 Move#getAllPositions의 네이밍을 수정한다
Cl8D Mar 20, 2023
69ded27
feat: 뷰와 도메인의 의존성을 줄인다
Cl8D Mar 20, 2023
c0a18ac
refactor: move 패키지를 piece의 하위 패키지로 이동한다
Cl8D Mar 20, 2023
b8b123a
feat: 현재 위치와 입력받은 위치가 동일한지 판단하는 기능을 추가한다
Cl8D Mar 20, 2023
947f6a0
feat: position이 over 되었는지에 대한 조건을 position에서 관리하도록 변경한다
Cl8D Mar 20, 2023
5a2d334
refactor: 각 체스말들에 대한 구체 클래스를 생성하고, MoveRule을 주입받는 형식으로 변경한다
Cl8D Mar 20, 2023
671085f
refactor: 패키지 이동을 진행한다
Cl8D Mar 20, 2023
f857825
feat: chessGame에서 폰에 대한 이동을 검증하는 기능을 제거한다
Cl8D Mar 20, 2023
3807a70
feat: 각 체스말이 가로 막혔을 때의 행위를 체스판에서 진행하도록 변경한다
Cl8D Mar 20, 2023
9a26d3d
refactor: chessGame 로직의 파라미터 수를 줄인다
Cl8D Mar 20, 2023
fb354ed
refactor: 컨트롤러와 start, end, move 상태에 대한 이름을 변경한다
Cl8D Mar 20, 2023
5d34902
refactor: 잘못된 테스트 코드 네이밍 수정
Cl8D Mar 21, 2023
64c1803
refactor: 메서드의 파라미터 수를 줄인다
Cl8D Mar 21, 2023
a1438cb
feat: 폰의 공격 조건에 대하여 추가한다
Cl8D Mar 21, 2023
fc3e731
feat: 폰을 이동시키는 행위에 대해 검증 기능을 추가한다
Cl8D Mar 21, 2023
1b2d7e9
test: 각 말의 공격에 대한 테스트를 추가한다
Cl8D Mar 21, 2023
02cbed3
refactor: 진영을 비교하는 기능의 네이밍을 변경한다
Cl8D Mar 22, 2023
aa3786a
refactor: 각 기물의 이동에 대한 클래스 이름을 변경한다
Cl8D Mar 22, 2023
cf6475c
refactor: Status 인터페이스의 네이밍을 변경한다
Cl8D Mar 22, 2023
36ee143
feat: 위치에 대한 서비스 레이어를 추가한다
Cl8D Mar 22, 2023
eb36fae
feat: 기물에 대한 서비스 레이어를 추가한다
Cl8D Mar 22, 2023
26e3db4
feat: 체스판에 대한 서비스 레이어를 추가한다
Cl8D Mar 22, 2023
8814a1a
feat: 도메인과 뷰의 의존성을 제거한다
Cl8D Mar 22, 2023
d75f190
feat: 필드에 대해서 연쇄 호출을 제거한다
Cl8D Mar 22, 2023
1753cf5
fix: 체스말의 존재 검증 로직을 수정한다
Cl8D Mar 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 180 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,183 @@
# java-chess
# 🪜 체스 게임 구현 미션

체스 미션 저장소
## Pair: 져니 [⛄️](http://github.com/cl8d), 제나 [❤️](https://github.com/yenawee)

## 우아한테크코스 코드리뷰
## ✔ 기능 요구사항

- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
### ChessBoard

- [x] 체스판을 초기화한다.
- [x] 체스 말의 위치에 대한 정보를 관리한다.
- [x] 해당 위치에 체스말이 존재하는지 확인한다.
- [x] 특정 위치에 존재하는 체스말을 반환한다.
- [x] 특정 위치에 존재하는 체스말을 제거한다.
- [x] 특정 위치에 체스말을 둔다.
- [x] 특정 말의 종류에 따라서 시작 위치에서 종료 위치로 이동 가능한지 판단한다.

### ChessGame

- [x] 체스 게임을 진행한다.

### CampType

- [x] 사용자의 진영을 관리한다.
- [x] 입력받은 위치의 열이 소문자면 WHITE, 대문자면 BLACK 진영으로 나눈다.
- [x] 플레이할 진영을 번갈아가며 반환한다.
joseph415 marked this conversation as resolved.
Show resolved Hide resolved

### Piece

- [x] 체스말이 어떤 진영에 속하는지 관리한다.
- [x] 두 개의 체스말이 동일한 진영에 속하는지 판단한다.
- [x] 체스말이 입력받은 진영에 속하는지 판단한다.
- [x] 체스말이 시작 위치에서 도착 위치로 이동 가능한지 판단한다.
- [x] 체스말이 공격 가능한지 판단한다.

### PieceType

- [x] 체스말의 종류를 관리한다.
- [x] 특정 체스말이 시작 위치에서 종료 위치까지 이동 가능한지 판단한다.

### Position

- [x] 체스판 위의 위치 정보를 저장한다.
- [x] 목표 위치를 반환한다.
- [x] 위치가 입력받은 제한값을 넘어가는지 판단한다.
- [x] 목표 위치로 이동하기 위한 단위 벡터를 계산한다.
- [x] 입력받은 위치를 복사한 새로운 위치를 반환한다.
- [x] 현재 위치와 입력받은 위치의 rank의 차이를 계산한다.
- [x] 현재 위치와 입력받은 위치의 file의 차이를 계산한다.
- [x] 현재 위치의 rank가 입력받은 위치의 rank보다 큰지 판단한다.
- [x] 현재 위치의 rank과 입력받은 위치의 rank가 동일한지 판단한다.
- [x] 현재 위치와 입력받은 위치가 동일한지 판단한다.

### Direction

- [x] 체스말이 이동하는 방향으로 관리한다.
- [x] 모든 방향에 대해서 반환한다.
- [x] 상하좌우에 대해서 반환한다.
- [x] 상하좌우의 대각선에 대해서 반환한다.

### Movable

- [x] 체스말의 움직임을 관리한다.
- [x] 체스말이 공격 가능한지 판단한다.

### Location

- [x] 이동 가능한 모든 위치에 대해 관리한다.
- [x] 이동 가능한 위치에 새로운 정보를 추가한다.
- [x] 이동 가능한 위치에 입력받은 위치가 포함되어 있는지 확인한다.

### Move
joseph415 marked this conversation as resolved.
Show resolved Hide resolved

- [x] 입력받은 위치로 이동시킨다.
- [x] 체스말이 이동 가능한 위치를 전부 반환한다.

### QueenMove

- [x] 입력받은 위치에 대해서 퀸이 이동 가능한지 판단한다.

### RookMove

- [x] 입력받은 위치에 대해서 룩이 이동 가능한지 판단한다.

### BishopMove

- [x] 입력받은 위치에 대해서 비숍이 이동 가능한지 판단한다.

### KingMove

- [x] 입력받은 위치에 대해서 킹이 이동 가능한지 판단한다.

### KnightMove

- [x] 입력받은 위치에 대해서 나이트가 이동 가능한지 판단한다.

### PawnMove

- [x] 입력받은 출발 위치의 행보다 도착 위치의 행이 더 크면 UP 방향, 아니면 DOWN 방향으로 이동할 수 있는지 판단한다.

### PositionConverter

- [x] 입력받은 source, target 위치를 가로, 세로 값으로 분리한다.
- [x] 가로값은 왼쪽부터 0~7 세로값은 아래부터 0~7으로 변환한다.
- [x] 사용자가 입력한 위치를 검증한다.
- [x] 입력받은 위치 명령어의 길이가 2인지 확인한다.
- [x] 첫 번째 글자가 a~h, 두 번째 글자가 1~8인지 확인한다.

### InputView

- [x] 명령어를 입력받는다.

### Command

- [x] 사용자가 입력한 명령어에 대해서 관리한다.
- [x] 사용자가 입력한 명령어가 start, move, end인지 검증한다.
- [x] 사용자가 입력한 명령어가 start인지 확인한다.
- [x] 사용자가 입력한 명령어가 end인지 확인한다.
- [x] 사용자가 입력한 명령어가 move일 때 올바른 명령어 길이로 들어오는지 확인한다.

### CommandType

- [x] 명령어의 종류에 대해서 관리한다. (start, move, end)

### Status

- [x] 사용자가 입력한 명령어에 따라 게임의 상태를 확인한다.
- [x] 현재 게임이 실행 중인지 판단한다.

### EndController

- [x] 사용자가 입력한 명령어가 End가 아니라면 예외를 발생시킨다.
- [x] 현재 게임이 실행 중인지 판단한다.

### MoveController

- [x] 사용자가 입력한 명령어가 Start라면 예외를 발생시키고, End라면 종료한다.
- [x] 사용자가 입력한 체스말을 시작 지점에서 끝 지점으로 이동시키고, 차례를 변경한다.
- [x] 현재 게임이 실행 중인지 판단한다.

### StartController

- [x] 사용자가 입력한 명령어가 Move라면 예외를 발생시키고, End라면 종료한다.
- [x] 현재 게임이 실행 중인지 판단한다.

### PieceName

- [x] 체스말의 종류에 따라 체스말의 이름으로 변환한다.

### OutputView

- [x] 게임 안내 메시지를 출력한다.
- [x] 체스판에 있는 체스말을 출력하고, 비어있는 곳은 .으로 출력한다.
- source에 있는 말이 target으로 이동된 체스판을 출력한다.

---

## ✔ 프로그래밍 요구사항

- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Java Style Guide을 원칙으로 한다.
- indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 3항 연산자를 쓰지 않는다.
- else 예약어를 쓰지 않는다.
- else 예약어를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
- 힌트: if문에서 값을 반환하는 방식으로 구현하면 else 예약어를 사용하지 않아도 된다.
- 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
- UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
- 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
- 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- 배열 대신 컬렉션을 사용한다.
- 모든 원시 값과 문자열을 포장한다
- 줄여 쓰지 않는다(축약 금지).
- 일급 컬렉션을 쓴다.
- 모든 엔티티를 작게 유지한다.
- 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
- 도메인의 의존성을 최소한으로 구현한다.
- 한 줄에 점을 하나만 찍는다.
- 게터/세터/프로퍼티를 쓰지 않는다.
- 모든 객체지향 생활 체조 원칙을 잘 지키며 구현한다.
- 프로그래밍 체크리스트의 원칙을 지키면서 프로그래밍 한다.
10 changes: 10 additions & 0 deletions src/main/java/chess/ChessApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package chess;

import chess.controller.ChessHandler;

public final class ChessApplication {
public static void main(String[] args) {
final ChessHandler chessHandler = new ChessHandler();
chessHandler.run();
}
}
40 changes: 40 additions & 0 deletions src/main/java/chess/controller/ChessHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package chess.controller;

import chess.controller.dto.BoardDto;
import chess.controller.status.StartController;
import chess.controller.status.Status;
import chess.domain.chess.ChessGame;
import chess.view.InputView;
import chess.view.OutputView;

import java.util.List;

public final class ChessHandler {

public void run() {
OutputView.printStartMessage();
final ChessGame chessGame = new ChessGame();
start(chessGame);
}

private void start(final ChessGame chessGame) {
Status gameStatus = new StartController(chessGame);
while (gameStatus.isRun()) {
gameStatus = play(chessGame, gameStatus);
}
}

private Status play(final ChessGame chessGame, Status gameStatus) {
try {
List<String> commands = InputView.getCommand();
final Command command = Command.findCommand(commands);
gameStatus = gameStatus.checkCommand(command,
() -> OutputView.printBoard(BoardDto.from(chessGame.getChessBoard())));
return gameStatus;
} catch (IllegalArgumentException e) {
OutputView.print(e.getMessage());
return play(chessGame, gameStatus);
}
}
}

46 changes: 46 additions & 0 deletions src/main/java/chess/controller/Command.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package chess.controller;

import java.util.Arrays;
import java.util.List;

public final class Command {
private static final int COMMAND_INDEX = 0;
private static final int MOVE_COMMAND_SIZE = 3;
private static final String COMMAND_ERROR_MESSAGE = "잘못된 명령어 입력입니다.";

private final CommandType type;
private final List<String> commands;

public Command(final CommandType type, final List<String> commands) {
this.type = type;
this.commands = commands;
}

public static Command findCommand(final List<String> commands) {
final CommandType type = Arrays.stream(CommandType.values())
.filter(e -> e.name().equalsIgnoreCase(commands.get(COMMAND_INDEX)))
.findAny()
.orElseThrow(() -> new IllegalArgumentException(COMMAND_ERROR_MESSAGE));
return new Command(type, commands);
}

public boolean isStart() {
return type == CommandType.START;
}

public boolean isMove() {
return type == CommandType.MOVE;
}

public boolean isEnd() {
return type == CommandType.END;
}

public boolean isCorrectWhenMove() {
return commands.size() == MOVE_COMMAND_SIZE;
}

public List<String> getCommands() {
return List.copyOf(commands);
}
}
5 changes: 5 additions & 0 deletions src/main/java/chess/controller/CommandType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package chess.controller;

public enum CommandType {
START, MOVE, END
}
30 changes: 30 additions & 0 deletions src/main/java/chess/controller/dto/BoardDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package chess.controller.dto;

import chess.domain.piece.Piece;
import chess.domain.piece.Position;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public final class BoardDto {

private final Map<PositionDto, PieceDto> board;

private BoardDto(final Map<PositionDto, PieceDto> board) {
this.board = board;
}

public static BoardDto from(final Map<Position, Piece> board) {

Choose a reason for hiding this comment

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

개인적으로는 dto로 변경하는 로직 조차도 엄격하게 도메인을 모르게만들고싶은데,,ㅎ 요거는 제 욕심이라 의견으로만 남겨둡니다!

Copy link
Author

@Cl8D Cl8D Mar 22, 2023

Choose a reason for hiding this comment

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

서비스 레이어를 두면 좋을 것 같은데, 그런 의도로 말씀 주신 거 맞을까요~?! 생성해두겠습니다 ㅎㅎㅎ 👍
+) 그런데 혹시, dto 역시 도메인을 모르게 하는 이유가 있을까요? 사실 dto는 도메인의 필드값들과 밀접하게 관련이 있을 수밖에 없기 때문에 도메인에 대한 업데이트가 발생하면 dto도 변경이 발생하는 게 당연하다고 생각이 들었거든요!

아니면, 이런 이유일까요?
[dto가 도메인을 알고 있는 경우]

1. 도메인 - dto 작성 후 1차 배포
2. 2차 배포 시 도메인 영역에 대한 수정이 발생함 (ex. 필드 제거) -> 도메인 수정 후 배포
3. dto가 도메인 자체를 참조하고 있을 경우, 해당 dto에서도 변경 발생

[dto가 도메인을 모를 경우 - 필드로 각 요소를 주입받음]

1, 2번 동일
3. dto가 주입받은 필드가 도메인에서 사용되는 필드였음 
-> 서비스에서 해당 필드 주입 제거 전까지는 dto가 완전한 상태임

제가 제대로 이해한 게 맞는지 궁금합니다!

Choose a reason for hiding this comment

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

제가 의도한거는 서비스를 만들라는 것은 아니였고, controller 에서 직접 new BoradDto(..) 선언해서 사용하는 것을 말씀드린거에요!
아니면 Mapper class를 만들어서 해당 클레스에서 Domain을 받고 Dto를 리턴해주는 형태로사용하던가요!

Choose a reason for hiding this comment

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

그리고, 지금 서비스를 잘못사용하고 계신것 같습니다. 현재 서비스를 dto를 만드는 factory처럼 사용하고 계시는데요ㅎㅎ.. 서비스의 역할은 이게 아닙니다.

서비스의 역할을 찾아보시면 좋을 것 같아요.

서비스는 비지니스 로직의 흐름을 담당하는 클래스입니다. dto를 만드는 factory 역할을 하는게 아닙니다!
원래는 서비스가 없다면 Controller에서 비지니스로직을 호출하여 사용하게 될텐데요. 애플리케이션이 커지면, 복잡해지기 때문에 이런 흩어진 로직을 하나로 묶어서 고수준의 형태로 추상화해서 사용하는 것이 서비스입니다.

따라서 비지니스 로직의 흐름을 고수준으로 묶기 때문에 서비스의 메서드는 하나의 applicaiton 의 기능을 하게 되는것이죠.

따라서 해당 구현들은 지금 잘못된것 같아요. 네이밍을 그냥 Mapper 기능을 하고 있어서 네이밍만 변경해서 사용하시면될 것 같아요.
그리고 Mapper의 utils 성 클래스이기 때문에, 인스턴습변수도 필요 없어질 것 같습니다

Choose a reason for hiding this comment

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

dto 는 view <-> controller 사이에서 도메인의 의존을 끊기 위해서 사용하는 것인데, dto가 도메인을 알면 사용하는 의미 자체가 퇴색되는것 같아요.

이런 의존성을 끊어주는 이유는 서로의 변경사항을 직접적으로 영향받지 않도록 하기 위함입니다.

domain이 변경돼서 view가 직접적으로 영향을 받거나, view의 변경으로 domain이 직접적으로 영향을 받거나 하는 상황이요.
도메인이 변경돼서 view가 아니라 dto가 변경이되는거면 의존성을 잘 끊어준것이 되겠죠?

만약 만약에

  1. dto로 도메인엔티티 전체가 아니고 일부만 내보내고 있다고 가정해볼게요. 필요한거는 일부분인데 굳이 필요하지 않은 변경사항때문에 재배포를 해야할 수 도 있습니다.
  2. 도메인은 변화에 유연하게 대처해야합니다. 만약에 도메인이 그대로 외부로 나가게 됐는데, 해당 도메인의 전체를 갈아 엎어야하는경우가 생기게 됐는데, 사용자가 버전업데이트를 하지 않으면, 해당 버전을 사용하는 사용자를 위해서 변경해야할 도메인은 변경하지 못합니다. (변경해버리면, 해당 사용자는 버그가 나겠죠?, 이 때문에 레거시가 계속 남게됩니다.)
  3. 의존성을 잘 관리하는 이유는 각 영역간의 영향성을 최소화 하기 위함입니다. view는 사용자의 영역이고, domain 은 가장 중요한 비지니스 영역인데 이 둘이 의존성이 엮어서 서로에게 영향을 줘야할 이유가 없습니다!
  4. 아래 이유

이동욱님 블로그 글을 참고로 달아둘게요.

여기서 Entity 클래스와 거의 유사한 형태임에도 DTO 클래스를 추가로 생성했는데요.
절대로 테이블과 매핑되는 Entity 클래스를 Request/ Response 클래스로 사용해서는 안됩니다.
Entity 클래스는 가장 Core한 클래스라고 보시면 되는데요.
수많은 서비스 클래스나 비지니스 로직들이 Entity 클래스를 기준으로 동작합니다.
Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되는 반면 Request와 Response용 DTO는 View를 위한 클래스라 정말 자주 변경이 필요합니다.

이런 이유들은 지금같은 콘솔프로그램같은 작은 서비스에선 와닿지 않을거에요!.. 이해가 안되더라도 경험하시면서 이해하도 좋을 것 같아요..

Copy link
Author

@Cl8D Cl8D Mar 23, 2023

Choose a reason for hiding this comment

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

서브웨이의 의견을 읽고 제가 이해한대로 말씀을 드려도 괜찮을까요?

1. 서비스의 역할에 대해
서비스는 “비즈니스 로직”을 담는다.
이유 -> 컨트롤러에서 호출하기 위한 비즈니스 로직들을 모아서 관리하기 위해 + 추상화 목적

여기서 말씀해 주신 추상화의 목적은 다음과 같을까요?
ex) 주문 관리 서비스

  1. OrderService라는 하나의 주문 서비스에 대한 인터페이스 생성
  2. 식품에 대한 FoodOrderService, 옷에 대한 ClothOrderService가 구현체로 있다.
  3. 두 서비스는 접근하는 엔티티가 다르다. 전자는 Food 엔티티, 후자는 Cloth 엔티티에 대해서 접근한다.
  4. 단순히 주문 자체에 대한 api는 주문 서비스에 대해서만 알고 있으면 된다. 추상화된 OrderService를 활용하여 주문을 진행한다.
    이런 식으로 생각하면 될까요?

2. dto에서 도메인을 알면 안 되는 이유
아래에서 설명해주신 이유는 뷰가 도메인 자체를 의존하면 안 되는 이유라고 생각해요. 그래서 dto를 사용하는 것까지는 저도 동의하는 바예요. (특히 웹 서비스로 가게 된다면 당연히 엔티티의 모든 내용을 내려주는 건 안 되니까 dto를 통해서 반환하는 것이겠구요. 순환 참조의 문제도 있을 것이구요.)

다만, 제가 궁금한 건 dto로 변환하는 로직에서, dto가 도메인을 자체를 가지고 변환 작업을 하면 안 되는 이유가 궁금했어요. 서브웨이는 dto도 일종의 뷰의 영역으로 보고 말씀하시는 것일까요?…
그렇다면 도메인의 변화가 생겼을 때 뷰와 밀접하게 관련이 있는 dto가 바로 영향을 받도록 하지 않고, 한 단계를 두어서 변화를 최대한 받지 않도록 하기 위해서라고 보면 될까요??…

Copy link
Author

@Cl8D Cl8D Mar 23, 2023

Choose a reason for hiding this comment

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

우선 서비스가 dto를 변환하는 건 옳지 않다는 것까지는 이해했어요! 다만... 별도의 매핑 클래스까지 두는 게 맞을까? 라는 의문이 들었습니다!

Copy link

@joseph415 joseph415 Mar 25, 2023

Choose a reason for hiding this comment

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

1번

네 맞습니다.

서비스가 없으면 컨트롤러에서 세부 구현들이 들어가겠죠!

controller {

orderFood() {
1. 주문 객체를 만든다.
2. 주문과 관련된 비지니스 로직들을 처리한다.
3.  ...
4. ...
}

orderCloth() {
1. 주문 객체를 만든다.
2. 주문과 관련된 비지니스 로직들을 처리한다.
3.  ...
4. ...
}
}

이런 행위들이 분산될 거고, 이걸 하나의 기능으로 추상화해서 사용하기 위해 서비스를 사용합니다. 따라서 서비스 하나의 애플리케이션 기능을 나타낸다고 할 수 있고, 비지니스 로직의 흐름을 담당한다고 할 수 있습니다.

2번

public static BoardDto from(final Map<Position, Piece> board) 이처럼 변환하는 로직 자체를 왜 알지 못하게 해야하는가? 에대한 질문인가요?

저는 dto에 대해서 이러한 생각을 갖고 있습니다.

  1. dto는 data trasnfer object로 view에 필요한 데이터를 객체로 넘겨주기 위해 사용하는 역할이기 때문에 dto에는 getter,setter 이외에 어떤 로직, 어떤 의존성도 안들어갔으면 합니다.
  2. 위와 같은 이유로 dto는 view에 필요한 데이터를 넘겨주기만 하면 되는데, 굳이 불필요한 도메인 의존이 들어가야 하나? 라는 생각이 듭니다. (dto와 도메인 패키지간의 결합이 생기는 것이 불필요한 의존이라고 생각해요!)
  3. 그리고 사실 변환하는 책임은 컨트롤러의 책임이 아닌가생각합니다!
  4. 마지막으로 매핑클래스를 두는것은 컨트롤러에서 가공하는 역할들이 많아지게 됐을 때, Mapper 클래스를 둬서 컨트롤러의 책임을 분배시도록 할 수 있다고 생각합니다.

domain의 영향이 dto에 영향을 끼쳐서라기보단, 위와 같은 이유로dto가 domain을 알아야 하나 라는 생각을 했습니다.

현재 변환로직이 단순 생성자로 변환하는것 말고는 따로 없고, 변환로직이 들어가는 것에 대해 어떻게 생각하느냐에 따라 달라질 것 같아서 그냥 의견으로 드렸던 것입니다!

만약 변환하는 메서드가 로직을 갖고 있었다면, 변경해달라고 리뷰를 하긴했을 것 같네요!

final Map<PositionDto, PieceDto> boardDto = new HashMap<>();
for (Position position : board.keySet()) {
boardDto.put(PositionDto.from(position.getRank(), position.getFile()),
PieceDto.from(board.get(position)));
}
return new BoardDto(boardDto);
}

public Map<PositionDto, PieceDto> getBoard() {
return Collections.unmodifiableMap(board);
}
}
27 changes: 27 additions & 0 deletions src/main/java/chess/controller/dto/PieceDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package chess.controller.dto;

import chess.domain.chess.CampType;
import chess.domain.piece.Piece;
import chess.domain.piece.PieceType;

public final class PieceDto {
private final PieceType pieceType;
private final CampType campType;
joseph415 marked this conversation as resolved.
Show resolved Hide resolved

private PieceDto(final PieceType pieceType, final CampType campType) {
this.pieceType = pieceType;
this.campType = campType;
}

public static PieceDto from(final Piece piece) {
return new PieceDto(piece.getPieceType(), piece.getCampType());
}

public boolean isSameCamp(final CampType campType) {
return this.campType == campType;
}

public PieceType getPieceType() {
return pieceType;
}
}
31 changes: 31 additions & 0 deletions src/main/java/chess/controller/dto/PositionDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package chess.controller.dto;

import java.util.Objects;

public final class PositionDto {

private final int rank;
private final int file;

private PositionDto(final int rank, final int file) {
this.rank = rank;
this.file = file;
}

public static PositionDto from(final int rank, final int file) {
return new PositionDto(rank, file);
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final PositionDto that = (PositionDto) o;
return rank == that.rank && file == that.file;
}

@Override
public int hashCode() {
return Objects.hash(rank, file);
}
}
15 changes: 15 additions & 0 deletions src/main/java/chess/controller/status/EndController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package chess.controller.status;

import chess.controller.Command;

public final class EndController implements Status {
@Override
public Status checkCommand(final Command command, final Runnable runnable) {
throw new IllegalArgumentException("게임이 끝났습니다.");
}

@Override
public boolean isRun() {
return false;
}
}
Loading