Skip to content

[다리 건너기] 4주차 미션 리펙토링 #1

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

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d2f23c7
chore(.gitignore): macos, java 관련 내용 추가
apptie Dec 5, 2022
178292b
docs(docs/README.md): 기능 목록 추가
apptie Dec 5, 2022
621de17
feat(BridgeTile): 각 다리의 칸을 관리하는 기능 추가
apptie Dec 5, 2022
a591144
test(BridgeTileTest): BridgeTile 테스트 추가
apptie Dec 5, 2022
05d438b
feat(MovingStep): 플레이어의 다리 이동 유무를 표현하는 기능 추가
apptie Dec 5, 2022
3390a86
test(MovingStepTest): MovingStep 테스트 추가
apptie Dec 5, 2022
4fe2890
feat(BridgeTile): 현재 다리 칸과 동일한 다리 칸인지 확인하는 로직 추가
apptie Dec 5, 2022
3a2cec0
test(BridgeTileTest): BridgeTile 기능 추가로 인한 테스트 케이스 추가
apptie Dec 5, 2022
1043dba
feat(MovingStepHistory): 플레이어의 이동 경로 메세지를 관리하는 기능 추가
apptie Dec 5, 2022
d9f6d4a
test(MovingStepHitoryTest): MovingStepHistory 테스트 추가
apptie Dec 5, 2022
d80e5e4
feat(PlayerState): 플레이어의 현재 게임 상태 정보를 관리하는 기능 추가
apptie Dec 5, 2022
811ca2b
test(PlayerStateTest): PlayerState 테스트 추가
apptie Dec 5, 2022
99a031b
refactor(BridgeTile): mapToCommand 메소드 로직 변경
apptie Dec 5, 2022
d5c9b1f
test(BridgeTileTest): BridgeTile 로직 변경으로 인한 테스트 케이스 수정
apptie Dec 5, 2022
ace1bda
feat(BridgeMaker): 외부에서 다리 생성 전략을 받아 다리를 생성하는 기능 추가
apptie Dec 5, 2022
315e1aa
test(BridgeMakerTest): BridgeMaker 테스트 추가
apptie Dec 5, 2022
b357bbb
feat(Bridge): 다리 정보를 관리하는 기능 추가
apptie Dec 5, 2022
f9afb89
test(BridgeTest): Bridge 테스트 추가
apptie Dec 5, 2022
f9abe30
feat(Player): 플레이어 관련 정보를 관리하는 기능 추가
apptie Dec 5, 2022
883f021
test(PlayerTest): Player 테스트 추가 및 중복되는 필드 공통 처리
apptie Dec 5, 2022
b63c2bb
feat(GameStatus): 다리 건너기 게임 상태를 관리하는 기능 추가
apptie Dec 5, 2022
b0592ef
test(GameStatusTest): GameStatus 테스트 추가
apptie Dec 5, 2022
68e366c
feat(GameCommand): 게임을 다시 시도할지 여부를 선택하는 커맨드를 관리하는 기능 추가
apptie Dec 5, 2022
691ecff
test(GameCommandTest): GameCommand 테스트 추가
apptie Dec 5, 2022
108d561
feat(InputView): 입력 관련 안내 문구를 출력하고 플레이어의 입력을 받는 기능 추가
apptie Dec 6, 2022
e632b37
feat(OutputView): 플레이어에게 게임 진행 상황과 결과를 출력하는 기능 추가
apptie Dec 7, 2022
db5f8fa
feat(BridgeGame): 게임을 진행하고 게임 정보를 관리하는 기능 추가
apptie Dec 7, 2022
d34ae10
test(BridgeGameTest): BridgeGame 테스트 추가
apptie Dec 7, 2022
615ac5f
refactor(GameStatus): playable 메소드 조건식의 결과를 바로 반환하도록 변경
apptie Dec 7, 2022
82014b6
test(GameStatusTest): GameStatus 로직 변경으로 인한 테스트 케이스 삭제
apptie Dec 7, 2022
6677cfc
refactor(InputView): 누락된 메세지 추가
apptie Dec 7, 2022
bf56ff0
feat(IOViewMananger): InputView와 OutputView를 관리하는 기능 추가
apptie Dec 7, 2022
22b52c1
feat(GameController): GameStatus에 따라 필요한 데이터를 입력받거나 출력하며 게임을 진행하는 기능 추가
apptie Dec 7, 2022
b364ef6
feat(GameRunner): 게임을 실행하는 유틸리티 클래스 추가
apptie Dec 7, 2022
222b860
refactor(BridgeMaker): BridgeNumberGenerator를 사용해 숫자를 무작위로 생성하는 로직 변경
apptie Dec 7, 2022
eabcc75
refactor(OutputView): 게임의 성공 / 실패 유무 메세지 관리 기능을 별도의 enum으로 분리
apptie Dec 7, 2022
621cccf
refactor: InputView에서 출력하는 게임 시작 안내 문구를 OutputView에서 출력하도록 로직 변경
apptie Dec 7, 2022
1d7c76b
refactor(OutputView): refactor(OutputView): 게임의 성공 / 실패 유무 메세지 관리 로직 수정
apptie Dec 7, 2022
e07828e
refactor(BridgeMaker): 사용하지 않는 import문 제거
apptie Dec 7, 2022
fc1d8fc
docs(docs/REFACTORING.md): 리펙토링 관련 문서 추가
apptie Dec 7, 2022
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
58 changes: 53 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ build/
!**/src/main/**
!**/src/test/**

### macOS ###
.DS_Store

### STS ###
.apt_generated
.classpath
Expand All @@ -31,5 +28,56 @@ out/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### macOS Patch ###
# iCloud generated files
*.icloud

### Java ###
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
39 changes: 39 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 미션 - 다리 건너기

## 기능 목록

### 입력

- 다리의 길이를 입력하는 기능
- 입력 값이 숫자인지 검증하는 기능
- 입력 값이 `3 ~ 20` 사이의 숫자인지 검증하는 기능
- 이동할 칸을 입력하는 기능
- 입력 값이 한 글자의 대문자인지 검증하는 기능
- 입력 값이 `U` 또는 `D`인지 검증하는 기능
- 게임 재시작 유무를 입력하는 기능
- 입력 값이 한 글자의 대문자인지 검증하는 기능
- 입력 값이 `R` 또는 `Q`인지 검증하는 기능

### 출력

- 게임 시작 안내 문구를 출력하는 기능
- 다리 길이 입력 안내 문구를 출력하는 기능
- 이동할 칸 입력 안내 문구를 출력하는 기능
- 플레이어가 지금까지 이동한 경로를 출력하는 기능
- 게임 재시작 입력 안내 문구를 출력하는 기능
- 최종 게임 결과를 출력하는 기능
- 플레이어의 마지막 이동 경로를 출력하는 기능
- 플레이어 게임 성공 유무를 출력하는 기능
- 플레이어가 게임을 시도한 횟수를 출력하는 기능
- 예외 발생 시 예외를 출력하는 기능

### 로직

- 입력한 다리 길이를 가지는 다리를 생성하는 기능
- 플레이어가 다리를 이동하는 기능
- 플레이어가 입력한 대로 다리의 칸을 입력할 수 있는지 확인하는 기능
- 플레이어가 다리를 모두 건넜는지 확인하는 기능
- 게임을 재시작하는 기능
- 플레이어의 이동 경로를 초기화하는 기능
- 플레이어의 게임 시도 횟수를 `1` 증가시키는 기능
- 예외 발생 시 예외가 발생한 단계를 다시 진행하는 기능
9 changes: 9 additions & 0 deletions docs/REFACTORING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 미션 - 다리 건너기

## 리펙토링 목록

- `PlayerState`와 `Bridge`간 순환 참조 제거
- `GameCommand`에서 사용하고 있던 잘못된 이름 수정
- 해당 클래스에서 사용하는 상수 및 메세지를 모두 내부에서 관리
- 불필요한 `GameStatus` 일부 상태 삭제
- `GuideView`에서 출력하는 메세지를 `OutputView`로 통합
4 changes: 3 additions & 1 deletion src/main/java/bridge/Application.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package bridge;

import bridge.domain.game.GameStatus;

public class Application {

public static void main(String[] args) {
// TODO: 프로그램 구현
GameRunner.run(GameStatus.MAKE_BRIDGE);
}
}
23 changes: 0 additions & 23 deletions src/main/java/bridge/BridgeGame.java

This file was deleted.

16 changes: 8 additions & 8 deletions src/main/java/bridge/BridgeMaker.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package bridge;

import bridge.domain.bridge.BridgeTile;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
* 다리의 길이를 입력 받아서 다리를 생성해주는 역할을 한다.
*/
public class BridgeMaker {

private final BridgeNumberGenerator bridgeNumberGenerator;
Expand All @@ -13,11 +13,11 @@ public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) {
this.bridgeNumberGenerator = bridgeNumberGenerator;
}

/**
* @param size 다리의 길이
* @return 입력받은 길이에 해당하는 다리 모양. 위 칸이면 "U", 아래 칸이면 "D"로 표현해야 한다.
*/
public List<String> makeBridge(int size) {
return null;
return IntStream
.generate(bridgeNumberGenerator::generate)
.limit(size)
.mapToObj(BridgeTile::mapToCommand)
.collect(Collectors.toList());
}
}
22 changes: 22 additions & 0 deletions src/main/java/bridge/GameRunner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package bridge;

import bridge.controller.GameController;
import bridge.domain.game.GameStatus;
import bridge.view.IOViewResolver;
import bridge.view.InputView;
import bridge.view.OutputView;

public final class GameRunner {

private GameRunner() {
}

public static void run(GameStatus gameStatus) {
IOViewResolver ioViewResolver = new IOViewResolver(InputView.getInstance(), OutputView.getInstance());
GameController controller = new GameController(ioViewResolver);

while (gameStatus.playable()) {
gameStatus = controller.process(gameStatus);
}
}
}

Choose a reason for hiding this comment

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

지민님 혹시 Runnercontroller는 어떤 차이가 있나요??
지민님이 구현하고자 하는 모델은 어떤 모델인지 궁금합니다
저는 MVC 모델 규칙을 최대한 지키며 간단하게 만들려고 노력하고 있어요

Copy link
Owner Author

Choose a reason for hiding this comment

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

@eunkeeee
저 또한 MVC 모델 규칙을 지키면서 미션을 진행하고자 노력하고 있습니다.

다만 저는 애플리케이션의 생명 주기를 Controller에서 관리하는 것은 어울리지 않다고 생각했었습니다.
Controller는 단순히 게임 진행 상황 GameStatus에 따라 View에서 받은 데이터를 토대로 Model에서 로직을 수행하는 것이 전부라고 생각했고, 그 이상으로 책임을 가지는 것은 어색하다고 느꼈습니다.

그래서 Controller는 애플리케이션 사용자의 요청을 전달받고, 그 요청을 처리하고, 요청에 따른 응답을 GameStatus를 반환하도록 했고, 이러한 Controller를 애플리케이션의 생명 주기에 따라 호출하기 위한 유틸리티 클래스가 GameRunner라고 보시면 될 것 같습니다.

Choose a reason for hiding this comment

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

@apptie 아하.. 애플리케이션 생명 주기를 관리하는 변수인 GameStatus를 컨트롤러에서 분리한 것이군요..
객체에 대한 지민님의 고민이 많이 느껴져요 !

28 changes: 0 additions & 28 deletions src/main/java/bridge/InputView.java

This file was deleted.

23 changes: 0 additions & 23 deletions src/main/java/bridge/OutputView.java

This file was deleted.

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

import bridge.BridgeRandomNumberGenerator;
import bridge.domain.bridge.BridgeGame;
import bridge.domain.bridge.exception.WrongGeneratorException;
import bridge.domain.game.GameStatus;
import bridge.domain.player.exception.WrongBridgeTileException;
import bridge.dto.controller.ExitDto;
import bridge.dto.controller.MoveDto;
import bridge.dto.controller.RetryDto;
import bridge.dto.input.ReadBridgeSizeDto;
import bridge.dto.input.ReadGameCommandDto;
import bridge.dto.input.ReadMovingDto;
import bridge.dto.output.PrintExceptionDto;
import bridge.dto.output.PrintResultDto;
import bridge.view.IOViewResolver;
import bridge.view.exception.NotFoundViewException;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Supplier;

public class GameController {

private static final String APPLICATION_EXCEPTION_MESSAGE = "애플리케이션 구성에 문제가 발생했습니다.";

private final IOViewResolver ioViewResolver;
private final Map<GameStatus, Supplier<GameStatus>> gameStatusMappings;
private BridgeGame bridgeGame;

public GameController(final IOViewResolver ioViewResolver) {
this.ioViewResolver = ioViewResolver;
gameStatusMappings = new EnumMap<>(GameStatus.class);

initGameStatusMappings();
}

private void initGameStatusMappings() {
gameStatusMappings.put(GameStatus.MAKE_BRIDGE, this::makeBridge);
gameStatusMappings.put(GameStatus.GAME_PLAY, this::gamePlay);
gameStatusMappings.put(GameStatus.GAME_OVER, this::gameOver);
gameStatusMappings.put(GameStatus.GAME_EXIT, this::gameExit);

Choose a reason for hiding this comment

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

저번에 숫자야구 게임에서 설명해주셔서 이해했는데,
이런 방법은 어떻게 떠올리게 되었는지 궁금합니다

Copy link
Owner Author

Choose a reason for hiding this comment

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

@eunkeeee
처음에는 분기문으로 처리했었는데, 그랬더니 메소드가 좀 지저분해지기도 하고, 길어지더라고요...
그래서 해결방법이 있나 구글링을 해보니 분기문을 없애는 다양한 방법이 나와서 그 중 적당한 방식을 선택했었습니다!

Choose a reason for hiding this comment

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

@apptie 아하.. 저도 처음에는 신박한데 복잡해 보인다고 생각했었는데,
애플리케이션이 복잡해질수록 좋은 것 같네요.. 저도 적용해 봐야겠어요

}

public GameStatus process(GameStatus gameStatus) {
try {
return gameStatusMappings.get(gameStatus).get();

Choose a reason for hiding this comment

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

마지막에 .get()Supplier의 메서드가 맞나요??

Copy link
Owner Author

Choose a reason for hiding this comment

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

@eunkeeee
Supplier의 메소드가 맞습니다!

} catch (IllegalArgumentException e) {
return processGameException(e.getMessage(), gameStatus);
} catch (WrongGeneratorException | NotFoundViewException | WrongBridgeTileException e) {
return processApplicationException(e.getMessage());
} catch (IndexOutOfBoundsException | NullPointerException e) {
e.printStackTrace();
return processApplicationException(APPLICATION_EXCEPTION_MESSAGE);
}
}

private GameStatus processGameException(String message, final GameStatus gameStatus) {
ioViewResolver.outputViewResolve(new PrintExceptionDto(message));
return gameStatus;
}

private GameStatus processApplicationException(String message) {
System.out.println(message);
return GameStatus.APPLICATION_EXIT;
}

private GameStatus makeBridge() {
ReadBridgeSizeDto inputDto = ioViewResolver.inputViewResolve(ReadBridgeSizeDto.class);
BridgeRandomNumberGenerator generator = new BridgeRandomNumberGenerator();
bridgeGame = new BridgeGame(inputDto.getSize(), generator);

return GameStatus.GAME_PLAY;
}

private GameStatus gamePlay() {
ReadMovingDto dto = ioViewResolver.inputViewResolve(ReadMovingDto.class);
MoveDto moveDto = bridgeGame.move(dto);

ioViewResolver.outputViewResolve(moveDto.getPrintMapDto());
return moveDto.getNextGameStatus();
}

private GameStatus gameOver() {
ReadGameCommandDto inputDto = ioViewResolver.inputViewResolve(ReadGameCommandDto.class);
RetryDto retryDto = bridgeGame.retry(inputDto);

return retryDto.getNextGameStatus();
}

private GameStatus gameExit() {
ExitDto exitDto = bridgeGame.exit();

ioViewResolver.outputViewResolve(new PrintResultDto(exitDto));
return GameStatus.APPLICATION_EXIT;
}
}
Loading