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

[2단계 - 자동차 경주 리팩토링] 검프(김태정) 미션 제출합니다. #217

Merged
merged 27 commits into from
Feb 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
19d9f6f
refactor: Screen 추상클래스 인터페이스로 변경
Livenow14 Feb 6, 2021
3a3c1e3
refactor: Cars 클래스에서 필요한 값만 반환한다
Livenow14 Feb 6, 2021
880f835
refactor: domain에 있던 dto 클래스 view패키지르 이동
Livenow14 Feb 6, 2021
8cafb73
refactor:Winners 메소드 변경
Livenow14 Feb 6, 2021
e2980c4
refactor: 테스트를 위한 메소드 삭제
Livenow14 Feb 6, 2021
4bfd56e
refactor: code convention
Livenow14 Feb 6, 2021
919443e
refactor: Collections.singletonList() 사용
Livenow14 Feb 6, 2021
7e74c4f
refactor: 테스트클래스 @DisplayName 애노테이션으로 표현
Livenow14 Feb 8, 2021
778c305
refactor: Cars클래스 검증 실패시, 에러메세지 출력
Livenow14 Feb 8, 2021
86aca54
refactor: Car 클래스 검증 실패시, 에러메세지 출력
Livenow14 Feb 8, 2021
c4f2b1f
refactor: RacingGameMachine 클래스 검증 실패시, 에러메세지 출력
Livenow14 Feb 8, 2021
dd2a453
refactor: Winner검증 실패시, 에러메세지 출력
Livenow14 Feb 8, 2021
d66a7e7
refactor: Car 클래스 검증 순서 변경
Livenow14 Feb 8, 2021
2a42110
refactor: Cars 클래스 검증 순서 변경
Livenow14 Feb 8, 2021
071620f
refactor: RacingGameMachine 클래스 검증 순서 변경 및 검증 로직 추가
Livenow14 Feb 8, 2021
cd9d349
refactor: Winner클래스 검증 순서 변경
Livenow14 Feb 8, 2021
08c318f
refactor: RacingGameMachine 에러 출력 메세지 변경
Livenow14 Feb 8, 2021
bc3613d
refactor: 파라미터 부분 전체 final 처리
Livenow14 Feb 8, 2021
54caae2
feat: TryCount 객체를 만들어 시도 횟수에 대한 책임을 지정함
Livenow14 Feb 9, 2021
7d30beb
refactor: Position을 만들어 위치에 대한 책임을 지정함
Livenow14 Feb 9, 2021
426921d
docs: 리펙토링 목록 정리
Livenow14 Feb 9, 2021
22370bb
refacotr: dto 클래스명 변경
Livenow14 Feb 9, 2021
c0a24aa
refactor: int 값을 사용하던 로직을 Position 객레로 변경
Livenow14 Feb 9, 2021
5560c58
refactor: String으로 지정된 name Name 인터페이스로 변환
Livenow14 Feb 10, 2021
2608a07
refactor: code convention
Livenow14 Feb 10, 2021
15fe424
refactor: CarMoveStatusDto에서 사용하던 원시값 dto로 변경
Livenow14 Feb 10, 2021
782086b
feat: 중복된 이름 검증 로직 구현
Livenow14 Feb 10, 2021
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
9 changes: 6 additions & 3 deletions src/main/java/racing/controller/RacingController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import racing.domain.Cars;
import racing.domain.RacingGameMachine;
import racing.domain.name.CarNames;
import racing.domain.number.TryCount;
import racing.view.InputScreen;
import racing.view.InputView;

Expand All @@ -14,10 +16,11 @@ public void run() {
private RacingGameMachine initializeRacingGame() {
InputScreen inputScreen = new InputScreen();
inputScreen.showMessage();
String carNames = InputView.getNextLine();
String inputName = InputView.getNextLine();
CarNames carNames = CarNames.of(inputName);
Cars cars = Cars.generate(carNames);
inputScreen.showCountMessage();
int tryCounts = InputView.getNextInt();
return new RacingGameMachine(cars, tryCounts);
int tryCount = InputView.getNextInt();
return new RacingGameMachine(cars, new TryCount(tryCount));
}
}
40 changes: 28 additions & 12 deletions src/main/java/racing/docs/요구사항.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,45 @@
## 객체 협력 정리

![객체 협력 정리](../../../../../image/객체협력정리.jpeg)
## 객체 메시지 정리

## 객체 메시지 정리

- 게임 시작하라
- 경주를 진행하라
- 랜덤값을 반환해라
- 자동차 움직여라
- 입력하라
- 자동차 움직여라
- 입력하라
- 메뉴를 출력하라
- 자동차 위치를 반환하라
- 자동차 위치를 출력하라
- 자동차 위치를 출력하라
- 자동차 모음을 생성하라
- 우승자를 출력하라
- 우승자를 출력하라

## 구현할 기능 목록 정리

- [x] 객체 모델링
- [x] 쉼표를 기준으로 구분된 n(최소 2 이상)대의 보장된 자동차 이름을 입력받는다.
- [x] 보장된 자동차 이름(빈 문자열이 아닌 5자 이하의 영어로만 구성).
- [x] 쉼표가 아닌 다른 구분자가 들어오면 에러가 발생한다.
- [x] 보장된 자동차 이름(빈 문자열이 아닌 5자 이하의 영어로만 구성).
- [x] 쉼표가 아닌 다른 구분자가 들어오면 에러가 발생한다.
- [x] 최소 1회 이상의 이동 횟수를 입력받는다.
- [x] 음수, 실수, 문자열 등 유효하지 않은 값을 입력받으면 에러가 발생한다.
- [x] 음수, 실수, 문자열 등 유효하지 않은 값을 입력받으면 에러가 발생한다.
- [x] 게임이 한번 실행될때 tryCount가 줄어든다.
- [x] 각 자동차들은 이동 횟수만큼 전진할 수 있는 기회를 부여받는다.
- [x] 랜덥값을 생성한다
- [x] 전진 조건은 0에서 9 사이의 random 값을 구한 후 random 값이 4 이상인 경우이다.
- [x] 각 회차별 실행 결과를 출력한다.
- [x] 게임이 끝나면 우승자가 나온다.
- [x] 랜덥값을 생성한다
- [x] 전진 조건은 0에서 9 사이의 random 값을 구한 후 random 값이 4 이상인 경우이다.
- [x] 각 회차별 실행 결과를 출력한다.
- [x] 게임이 끝나면 우승자가 나온다.
- [x] 가장 멀리 이동한 1명 이상의 우승자 명단을 탐색해 출력한다.

## 리펙토링 목록 정리

Choose a reason for hiding this comment

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

피드백에 관련한 리팩토링 목록도 정리하셨네요 👍
멋집니다!

Copy link
Author

Choose a reason for hiding this comment

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

문서화를 습관화 하겠습니다. 😊


- [x] Screen 추상클래스 인터페이스로 변경
- [x] Dto를 어떻게 반환할 것인지, dto의 패키지 또한 변경한다. (Cars, Winners 객체)
- [x] 테스트를 위한 메소드를 삭제한다.
- [x] Collections.singletonList()를 사용한다.
- [x] 테스트 클래스에서 @DisplayName으로 테스트의 용도를 설명한다
- [x] 에러메세지 출력시 사용자가 어떤 값을 입력햇는지 또한 출력한다.
- [x] `할당 -> 검증`의 과정을 `복사(방어) -> 검증 -> 할당`의 과정으로 변환
- [x] 매개변수르 들어오는 값들을 final 처리
- [x] 원시값을 객체로 포장한다.
- [x] 중복 이름에 대해서 검증한다
42 changes: 18 additions & 24 deletions src/main/java/racing/domain/Car.java
Original file line number Diff line number Diff line change
@@ -1,47 +1,41 @@
package racing.domain;

import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import racing.domain.name.CarName;
import racing.domain.number.Position;

public class Car {
private static final Pattern PATTERN = Pattern.compile("[a-zA-Z]{1,5}");
private static final int MINIMUM_MOVE_NUMBER = 4;
private static final int DEFAULT_POSITION = 0;

private final String name;
private int position;
private final CarName name;
private final Position position;

public Car(String name) {
public Car(final CarName name) {
this.name = name;
this.position = DEFAULT_POSITION;
validateName();
this.position = Position.of();
}

private void validateName() {
if (Objects.isNull(this.name)) {
throw new IllegalArgumentException();
}
Matcher matcher = PATTERN.matcher(this.name);
if (!matcher.matches()) {
throw new IllegalArgumentException();
}
}

public boolean move(int randomNumber) {
public boolean move(final int randomNumber) {
if (randomNumber >= MINIMUM_MOVE_NUMBER) {
this.position++;
position.add();
return true;
}
return false;
}

public int getPosition() {
public int getPositionValue() {
return position.getValue();
}

public Position getPosition() {
return position;
}

public boolean isSamePosition(Position maxPosition) {
return position.equals(maxPosition);
}

public String getName() {
return name;
return name.getName();
}
}

62 changes: 36 additions & 26 deletions src/main/java/racing/domain/Cars.java
Original file line number Diff line number Diff line change
@@ -1,68 +1,78 @@
package racing.domain;

import racing.domain.dto.CarDto;
import racing.domain.name.CarName;
import racing.domain.name.CarNames;
import racing.domain.number.Position;
import racing.utils.RandomUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class Cars {
private static final String DELIMITER = ",";
private static final int SPLIT_THRESHOLD = -1;
public class Cars implements Cloneable {
private static final int START_NUMBER = 0;
private static final int END_NUMBER = 9;
private static final int MINIMUM_CAR_COUNTS = 2;
private static final int MINIMUM_CAR_SIZE = 2;

private final List<Car> cars;

private Cars(List<Car> cars) {
this.cars = new ArrayList<>(cars);
validateCars();
private Cars(final List<Car> cars) {
List<Car> copy = new ArrayList<>(cars);
validateCars(copy);
this.cars = copy;
}

public static Cars generate(String carNames) {
String[] splitCarNames = splitCarNames(carNames);
List<Car> cars = Arrays.stream(splitCarNames)
.map(Car::new)
public static Cars generate(final CarNames inputCarNames) {
List<CarName> carNames = inputCarNames.getCarNames();
List<Car> cars = carNames.stream()
.map(carName -> new Car(carName))
.collect(Collectors.toList());
return new Cars(cars);
}

private static String[] splitCarNames(String carNames) {
return carNames.split(DELIMITER, SPLIT_THRESHOLD);
private void validateCars(List<Car> cars) {
if (cars.size() < MINIMUM_CAR_SIZE) {
throw new IllegalArgumentException("자동차는 1개 이상이어야 합니다");
}
}

private void validateCars() {
if (cars.size() < MINIMUM_CAR_COUNTS) {
throw new IllegalArgumentException();
@Override
protected Cars clone() {
Copy link

@JunHoPark93 JunHoPark93 Feb 11, 2021

Choose a reason for hiding this comment

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

이부분은 질문을 하셨기에 남깁니다!
어떤 이유로 clone()을 오버라이드 하신건가요?
아마 리스트 반환에서 불변으로 반환을 하려고 하신거같은데 하단의 getCars()메서드에서 이미 그 기능을 하고있다고 생각해요!

그냥 참고로만 말씀드리면 객체를 생성자를 통해서 만드는것이 아니고 필드를 복사하며 만들기 때문에 불변을 보장하기가 어렵습니다. 이것으로 인한 버그들이 많다고알고있는데요, shallow copy를 통한 작업으로 인해 정말 의도한 대로 복사가 이루어지지 않을 수 있습니다.
추후 학습하시면서 아시게 될거에요!

Copy link
Author

Choose a reason for hiding this comment

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

  • 현재 RacingController에서 Cars를 생성하고, 생성한 객체를 RacingGameMachine의 생성자 인자로 넘겨주고 있습니다. (Cars객체의 생성의 책임울 RacingController에서 지니고 있음) 그렇기 때문에, 생성자 인자로 넘어온 Cars객체를 복사후 → 검증 → 할당의 과정을 거치게 하기위해 Cloneable 인터페이스를 구현한 것입니다.
  • 다시 한번 확인해보니, 깊은 복사인척을 하는 얇은 복사였던 것 같습니다. clone을 없애고, 새롭게 객체를 만들어 복사하는 형식으로 변경하였습니다. 객체는 생성자를 통해 만들어야한다 를 정확히 이해하게 되었습니다. 감사합니다.

Cars copy = null;
try {
copy = (Cars) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return copy;
}

public void race() {
cars.forEach(car -> car.move(RandomUtils.getRandomNumber(START_NUMBER, END_NUMBER)));
}

public Winners findWinners() {
int maxPosition = getMaxPosition();
Position maxPosition = getMaxPosition();
List<Car> winners = cars.stream()
.filter(car -> car.getPosition() == maxPosition)
.filter(car -> car.isSamePosition(maxPosition))
.collect(Collectors.toList());
return new Winners(winners);
}

private int getMaxPosition() {
private Position getMaxPosition() {
return cars.stream()
.max(Comparator.comparingInt(Car::getPosition))
.max(Comparator.comparing(Car::getPosition))
.orElseThrow(IllegalStateException::new)
.getPosition();
}

public List<CarDto> getCarDtos() {
return cars.stream()
.map(CarDto::of)
.collect(Collectors.toList());
public List<Car> getCars() {
return Collections.unmodifiableList(cars);
}

public int getSize() {
return cars.size();
}
}
39 changes: 21 additions & 18 deletions src/main/java/racing/domain/RacingGameMachine.java
Original file line number Diff line number Diff line change
@@ -1,44 +1,47 @@
package racing.domain;

import racing.domain.dto.CarDto;
import racing.domain.number.TryCount;
import racing.view.GameScreen;
import racing.view.dto.CarMoveStatusDto;
import racing.view.dto.WinnersStatusDto;

import java.util.List;
import java.util.stream.Collectors;

public class RacingGameMachine {
private static final int ZERO = 0;
private static final int MINIMUM_CAR_COUNTS = 2;

private final Cars cars;
private int tryCounts;
private final TryCount tryCount;

public RacingGameMachine(Cars cars, int tryCounts) {
this.cars = cars;
this.tryCounts = tryCounts;
validateTryCounts();
public RacingGameMachine(final Cars cars, final TryCount tryCount) {
Cars copy = cars.clone();
validateCars(copy);
this.cars = copy;
this.tryCount = tryCount;
}

private void validateTryCounts() {
if (tryCounts <= ZERO) {
throw new IllegalArgumentException();
private void validateCars(final Cars cars) {
if (cars.getSize() < MINIMUM_CAR_COUNTS) {
throw new IllegalArgumentException("자동차 이름은 1개 이상이어야 합니다");
}
}

public void play() {
GameScreen gameScreen = new GameScreen();
gameScreen.showMessage();
while (tryCounts-- > ZERO) {
while (tryCount.reduce()) {
cars.race();
gameScreen.showCarStatus(getCarDtos());
}
Winners winners = cars.findWinners();
gameScreen.showWinners(winners.getWinnersDto());
}

public boolean canPlay() {
return tryCounts > ZERO;
Winners winners = cars.findWinners();
gameScreen.showWinners(new WinnersStatusDto(winners.getWinnersName()));
}

private List<CarDto> getCarDtos() {
return cars.getCarDtos();
private List<CarMoveStatusDto> getCarDtos() {
return cars.getCars().stream()
.map(car -> CarMoveStatusDto.of(car))

Choose a reason for hiding this comment

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

Suggested change
.map(car -> CarMoveStatusDto.of(car))
.map(CarMoveStatusDto::of)

🙏

Copy link
Author

Choose a reason for hiding this comment

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

확인했습니다! 감사합니다.

.collect(Collectors.toList());
}
}
20 changes: 9 additions & 11 deletions src/main/java/racing/domain/Winners.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
package racing.domain;

import racing.domain.dto.WinnersDto;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Winners {
private final List<Car> cars;

public Winners(List<Car> cars) {
this.cars = new ArrayList<>(cars);
validateWinners();
public Winners(final List<Car> cars) {
List<Car> copy = new ArrayList<>(cars);
validateWinners(copy);
this.cars = copy;
}

public WinnersDto getWinnersDto() {
List<String> winnersName = cars.stream()
public List<String> getWinnersName() {
return cars.stream()
.map(Car::getName)
.collect(Collectors.toList());
return new WinnersDto(winnersName);
}

private void validateWinners() {
if (this.cars.isEmpty()) {
throw new IllegalStateException();
private void validateWinners(final List<Car> copy) {
if (copy.isEmpty()) {
throw new IllegalStateException("우승자는 1명 이상이어야 합니다");
}
}
}
25 changes: 0 additions & 25 deletions src/main/java/racing/domain/dto/CarDto.java

This file was deleted.

Loading