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

Step5 (MVC 리팩토링) 리뷰 요청드립니다. #913

Merged
merged 21 commits into from
May 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ccf4b29
docs (README) MVC 기반 내용 추가
xlffm3 May 22, 2020
0ed4e45
refactor : MVC 기반 패키지 수정
xlffm3 May 22, 2020
12b4e41
refactor (RacingGameTest) Arrays.deepEquals를 이용한 테스트를 간소화함
xlffm3 May 22, 2020
1e29191
style : test 메소드들 줄바꿈 추가
xlffm3 May 23, 2020
e033fb1
refacotr : MVC 패턴에 맞게 RacingGame 내부 View 의존 기능들을 분리, 게임 결과(Log) Test 추가
xlffm3 May 23, 2020
949cd10
feat (GameLog) Log 객체 생성 기능 구현
xlffm3 May 23, 2020
529b139
docs (README) controller 추가
xlffm3 May 23, 2020
1133907
feat : Controller 생성 및 리팩토링
xlffm3 May 23, 2020
768661d
refactor : this keyword 삭제 및 코드 간결화
xlffm3 May 23, 2020
ead5131
test (RacingGameTest) 추가 기능에 대한 테스트 추가
xlffm3 May 23, 2020
73a721a
feat (RacingGame) : 예외 처리 추가
xlffm3 May 23, 2020
aacf4f3
test (RacingGameControllerTest) controller의 작동 순서가 올바르지 않을때 에러 테스트
xlffm3 May 23, 2020
d6e4e45
feat (RacingGameController) 에러처리 기능 추가
xlffm3 May 23, 2020
b695493
refactor : 패키지 이름 수정
xlffm3 May 23, 2020
ee65186
docs (README) 오타 수정
xlffm3 May 23, 2020
d6b2f18
refactor (RacingGame) 조건문 코드 수정
xlffm3 May 23, 2020
8bc2807
refactor (CarTest) -> move Parameter로 전략을 받아 움직이게 변경하여 기능 테스트
xlffm3 May 23, 2020
0cfccce
refactor : Car 객체의 move 메소드 파라미터 추가로 인한 코드 수정
xlffm3 May 23, 2020
180451f
style 오탈자 수정 및 공백 추가
xlffm3 May 23, 2020
cc47dec
refactor : Car 객체가 movingStrategy를 가지게함
xlffm3 May 24, 2020
47eb163
style : 오타 수정
xlffm3 May 24, 2020
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
12 changes: 0 additions & 12 deletions src/main/java/step3/racingcar/Application.java

This file was deleted.

17 changes: 17 additions & 0 deletions src/main/java/step5/racingcar/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package step5.racingcar;

import step5.racingcar.controller.RacingGameController;
import step5.racingcar.view.InputView;
import step5.racingcar.view.ResultView;

public class Main {

public static void main(String[] args) {
InputView inputView = new InputView();
ResultView resultView = new ResultView();
RacingGameController racingGameController = new RacingGameController(inputView, resultView);

racingGameController.processUserInputs();
racingGameController.startGame();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,24 @@
* 자동차 객체의 Position을 이동시키기는 기능.
* 경기 결과를 출력하는 기능.
* 가장 멀리 주행한 자동차들의 리스트를 가져오고 출력하는 기능.

## MVC 기반 리팩토링

Choose a reason for hiding this comment

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

먼저 남겨주신 내용에 대해 먼저 답변 드리고 리뷰를 시작할게요!

기존 방식은 Random값 때문에 자동차 객체의 position이 정말로 움직였는지 확인하기가 힘들었는데요. (주로 containsAnyOf(0, 1)과 같이 애매한 코드...)

RacingGame이 설정한 규칙을 Car에게 전달해 move 요청을 시키면 테스트 하기도 용이해져서 수정했는데... 이게 혹시 Car 객체의 자율성을 너무 해치는 것일까요?

Car가 이동 규칙을 가지는 경우

Car 객체의 position이 명확히 움직이는지를 확인하는 테스트는 전략 패턴을 활용하셨으니 이동 규칙을 갈아끼워 주는 방식으로 테스트를 진행하시면 괜찮지 않을까요?

MovingStrategy는 FunctionalInterface라 람다를 이용해 Car 객체가 가지는 이동 규칙을 쉽게 바꿀 수 있을 것 같습니다.

Car의 단위 테스트에서 생성자로 테스트에서 정의한 새로운 MovingStrategy(무조건 이동한다거나..)를 만들어 테스트에 활용해보시면 어떨까요?

RaingGame이 Car의 이동 규칙을 가지는 경우

추가로 RacingGame의 규칙을 Car에게 전달하는 방식으로 사용한다고 하셔도 MovingStarategy가 RacingGame의 내부에 캡슐화되어 RacingGame 내부의 move 메소드에 대한 테스트는 여전히 힘들것 같습니다.

지금 구조로 진행하신다고 하면 RacingGame의 생성자로 MovingStrategy를 받고 테스트에서 RacingGame 생성 시점에 이동 전략을 교체해주는 방식으로 move까지 통합적인 테스트를 하고, 실제 어떤 자동차가 가장 멀리 이동했는지, 우승자가 누군지를 구하는 방법까지 테스트해주실 수 있을 것 같네요

이게 혹시 Car 객체의 자율성을 너무 해치는 것일까요?

조금 이건 개인적인 견해일 수 있는데요 , Car 각각의 이동 전략을 RacingGame이 통합적으로 컨트롤해주는 것 보다 그 각각의 이동 전략을 Car에 위임하는 방식이 조금 더 자연스럽다고 생각이 듭니다. (각각 Car 마다 이동 전략이 다를수도 있고.. Car 자기 자신이 이동하는데 이동에 대한 규칙을 외부에서 받는게 조금 부자연스러운 생각이 들어서요)

하지만 지금의 요구사항 안에서라면 RacingGame이 이동 규칙을 가지고 각각 자동차 이동 시 마다 규칙을 넘겨주어 이동 여부를 체크하고 각각의 자동차를 이동시키는 방법이 조금 더 효율적인 것 같긴 합니다.

Copy link
Author

Choose a reason for hiding this comment

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

자세한 피드백 감사합니다. 그런 방식으로도 테스트가 가능하겠군요!!


* Domain
* Car
* CarFactory
* CarGroups
* RacingGame
* MovingStrategy
* RandomMovingStrategy
* Message

* Controller
* RacingGameController
Comment on lines +27 to +28

Choose a reason for hiding this comment

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

이런 패키지 분리 등은 개인의 의도와 네이밍이 다 달라서 리뷰를 남기기 조심스러운데, 제가 생각했던 내용을 간단히 코멘트 남길게요 참고만 하시고 변경은 하시지 않아도 될 것 같아요

기존 Controller가 없을 때 Main에서 InputView를 호출해 입력을 받고, domain의 RacingGame을 이용해 결과를 받고, ResultView를 이용해 결과를 출력해주고 있었던걸로 기억합니다.

저는 그래서 이미 Main 자체가 Controller의 역할을 수행하고 있지 않았나? 하는 생각이 조금 들었어요!

Copy link
Author

Choose a reason for hiding this comment

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

아 네 맞습니다. Main 자체가 Controller 역할을 하고 있었는데요.
기존 Main 클래스에서 Controller 구조를 추가한 이유는... 특별하기 보다는 연습 차원에서 분리해본것이었습니다.


* View
* InputView
* ResultView

* Application
* Main
36 changes: 36 additions & 0 deletions src/main/java/step5/racingcar/controller/RacingGameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package step5.racingcar.controller;

import step5.racingcar.domain.Message;
import step5.racingcar.domain.RacingGame;
import step5.racingcar.view.InputView;
import step5.racingcar.view.ResultView;

public class RacingGameController {

private RacingGame racingGame;
private final InputView inputView;
private final ResultView resultView;

public RacingGameController(InputView inputView, ResultView resultView) {
this.inputView = inputView;
this.resultView = resultView;
}

public void processUserInputs() {
String[] carNames = inputView.getCarNames(Message.CAR_NAMES);
int gameTryCounts = inputView.getGameTryCounts(Message.GAME_TRY_COUNTS);
this.racingGame = new RacingGame(carNames, gameTryCounts);
}

public void startGame() {
if (racingGame == null) {
throw new IllegalStateException(Message.ERROR_INVALID_ORDER);
}
resultView.printResultHeader(Message.RESULT_HEADER);
while (racingGame.hasNextRound() == true) {
racingGame.run();
resultView.printResult(racingGame);
}
resultView.printWinnerCarNames(racingGame);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package step3.racingcar;
package step5.racingcar.domain;

public class Car {

private static final int DEFAULT_POSITION = 0;
private int position;
private final String name;
private final MovingStrategy movingStrategy;

private static final int DEFAULT_POSITION = 0;

public Car(String name, MovingStrategy movingStrategy) {
this.position = DEFAULT_POSITION;
this.name = name;
Expand All @@ -18,8 +19,8 @@ public Car(String name, MovingStrategy movingStrategy) {
public String getName() { return name; }

public void move() {
boolean isMovable = this.movingStrategy.isMovable();
boolean isMovable = movingStrategy.isMovable();
if (isMovable == true)
this.position++;
position++;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package step3.racingcar;
package step5.racingcar.domain;

import java.util.Arrays;
import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package step3.racingcar;
package step5.racingcar.domain;

import java.util.Comparator;
import java.util.List;
Expand All @@ -14,17 +14,17 @@ public CarGroups(List<Car> carGroups) {
}

public void move() {
this.carGroups.forEach(Car::move);
carGroups.forEach(Car::move);
}

public List<String> getCarNames() {
return this.carGroups.stream()
return carGroups.stream()
.map(Car::getName)
.collect(Collectors.toList());
}

public List<Integer> getCarPositions() {
return this.carGroups.stream()
return carGroups.stream()
.map(Car::getPosition)
.collect(Collectors.toList());
}
Expand All @@ -38,7 +38,7 @@ public List<String> getWinnerCarNames() {
}

private int getMaxPosition() {
Car car = this.carGroups.stream()
Car car = carGroups.stream()
.max(Comparator.comparing(Car::getPosition))
.orElseThrow(() -> new NoSuchElementException(Message.ERROR_MAX_POSITION));
return car.getPosition();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package step3.racingcar;
package step5.racingcar.domain;

public class Message {

Expand All @@ -9,6 +9,8 @@ public class Message {
public static final String ERROR_MAX_POSITION = "ERROR : 우승 차량을 찾을 수 없습니다.";
public static final String RESULT_HEADER = "실행 결과";
public static final String RESULT_FOOTER = "가 최종 우승했습니다.";
public static final String ERROR_INVALID_STATE = "ERROR : 더이상 플레이 할 수 없습니다.";
public static final String ERROR_INVALID_ORDER = "ERROR : 게임 실행 전 게임 정보를 입력 갱신하십시오.";

private Message() {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package step3.racingcar;
package step5.racingcar.domain;

public interface MovingStrategy {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package step3.racingcar;

import java.util.List;
package step5.racingcar.domain;

public class RacingGame {

private String[] carNames;
private int gameTryCounts;
private final CarGroups playerCars;

private static final String BLANK_NAME = "";
private static final int MINIMUM_LIMIT = 1;
private static final int INDEX_ZERO = 0;

public RacingGame(String[] carNames, int gameTryCounts) {
validateConstructors(carNames, gameTryCounts);
this.carNames = carNames;
this.gameTryCounts = gameTryCounts;
this.playerCars = new CarGroups(CarFactory.makeCars(carNames));
}

private void validateConstructors(String[] carNames, int gameTryCounts) {
Expand All @@ -24,20 +23,19 @@ private void validateConstructors(String[] carNames, int gameTryCounts) {
}

public void run() {
CarGroups playerCars = new CarGroups(CarFactory.makeCars(this.carNames));
ResultViewProcessor.printResultHeader(Message.RESULT_HEADER);
for (int i = INDEX_ZERO; i < gameTryCounts; i++) {
playerCars.move();
ResultViewProcessor.printResult(playerCars);
}
ResultViewProcessor.printWinnerCarNames(playerCars);
if (gameTryCounts < MINIMUM_LIMIT)
throw new IllegalStateException(Message.ERROR_INVALID_STATE);
playerCars.move();
gameTryCounts--;
}

public String[] getCarNames() {
return carNames;
public boolean hasNextRound() {
return gameTryCounts >= MINIMUM_LIMIT;
}

public int getGameTryCounts() {
return gameTryCounts;
}

public CarGroups getPlayerCars() { return playerCars; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package step3.racingcar;
package step5.racingcar.domain;

import java.util.Random;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
package step3.racingcar;
package step5.racingcar.view;

import java.util.Scanner;

public class InputViewProcessor {
public class InputView {

private static final Scanner scanner = new Scanner(System.in);
private static final String SEPARATOR = ",";

private InputViewProcessor() {
}

public static String[] getCarNames(String instructionMessage) {
public String[] getCarNames(String instructionMessage) {
System.out.println(instructionMessage);
String[] carNames = scanner.next().split(SEPARATOR);
return carNames;
}

public static int getGameTryCounts(String instructionMessage) {
public int getGameTryCounts(String instructionMessage) {
System.out.println(instructionMessage);
int userInput = scanner.nextInt();
return userInput;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
package step3.racingcar;
package step5.racingcar.view;

import step5.racingcar.domain.Message;
import step5.racingcar.domain.RacingGame;

import java.util.List;

public class ResultViewProcessor {
public class ResultView {

private static final String POSITION_MARK = "-";
private static final String DELIMITER = " : ";
private static final String COMMA = ", ";
private static final int INDEX_ZERO = 0;

private ResultViewProcessor() {
}

public static void printResultHeader(String headerMessage) {
public void printResultHeader(String headerMessage) {
System.out.println(headerMessage);
}

public static void printResult(CarGroups carGroups) {
List<String> carNames = carGroups.getCarNames();
List<Integer> carPositions = carGroups.getCarPositions();
public void printResult(RacingGame racingGame) {
List<String> carNames = racingGame.getPlayerCars().getCarNames();
List<Integer> carPositions = racingGame.getPlayerCars().getCarPositions();
int carCounts = carNames.size();
for (int i = INDEX_ZERO; i < carCounts; i++) {
System.out.print(carNames.get(i) + DELIMITER);
Expand All @@ -27,15 +27,15 @@ public static void printResult(CarGroups carGroups) {
System.out.println();
}

private static void printCurrentPosition(int currentPosition) {
private void printCurrentPosition(int currentPosition) {
for (int i = INDEX_ZERO; i < currentPosition; i++) {
System.out.print(POSITION_MARK);
}
System.out.println();
}

public static void printWinnerCarNames(CarGroups carGroups) {
List<String> winnerCarNames = carGroups.getWinnerCarNames();
public void printWinnerCarNames(RacingGame racingGame) {
List<String> winnerCarNames = racingGame.getPlayerCars().getWinnerCarNames();
StringBuilder stringBuilder = new StringBuilder();
for (String winnerCarName : winnerCarNames) {
appendComma(stringBuilder);
Expand All @@ -44,7 +44,7 @@ public static void printWinnerCarNames(CarGroups carGroups) {
System.out.println(stringBuilder.toString() + Message.RESULT_FOOTER);
}

private static void appendComma(StringBuilder stringBuilder) {
private void appendComma(StringBuilder stringBuilder) {
if (stringBuilder.length() > INDEX_ZERO)
stringBuilder.append(COMMA);
}
Expand Down
39 changes: 0 additions & 39 deletions src/test/java/step3/racingcar/CarTest.java

This file was deleted.

Loading