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

[스티치] 자동차 경주 게임 미션 코드리뷰 제출합니다. #93

Merged
merged 43 commits into from
Feb 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a842b20
feat : 문자열 덧셈 계산기에서 null과 공백 문자열을 0으로 처리하는 기능 구현
lxxjn0 Feb 11, 2020
c4c8e5f
feat : 문자열 덧셈 계산기에서 콤마와 콜론 구분자로 나누어서 더하는 기능 구현
lxxjn0 Feb 11, 2020
d80f88c
feat : 문자열 덧셈 계산기에서 커스텀 구분자로 나누어서 더하는 기능 구현
lxxjn0 Feb 11, 2020
79a5933
feat : 문자열 덧셈 계산기에서 숫자 이외의 값 또는 음수에 대해 RuntimeException throw 구현
lxxjn0 Feb 11, 2020
b70ee54
docs : README.md에 기능 구현 목록 작성
lxxjn0 Feb 11, 2020
271b121
feat : 자동차의 이름을 쉼표(,)로 나누는 기능 구현
lxxjn0 Feb 11, 2020
5430ccb
feat : 자동차 이름의 길이가 5자를 넘지 않는지 확인하는 기능 구현
lxxjn0 Feb 11, 2020
84a6723
feat : 이동 횟수 입력에 대한 유효성을 검증하는 기능 구현
lxxjn0 Feb 11, 2020
f96307b
feat : 0에서 9까지의 Random 값을 생성하는 기능 구현
lxxjn0 Feb 11, 2020
e0d1884
feat : Random 값에 따라 전진 여부를 결정하는 기능 구현
lxxjn0 Feb 11, 2020
c71fdeb
refactor : MovementStrategy 클래스 삭제
lxxjn0 Feb 12, 2020
24f3337
feat : Random 값에 따라 전진 여부를 결정하는 기능 구현
lxxjn0 Feb 12, 2020
be57040
docs : README.md의 기능 구현 목록 수정
lxxjn0 Feb 12, 2020
4a980d2
feat : 자동차가 전진하는 경우 위치를 1 증가시키는 기능 구현
lxxjn0 Feb 12, 2020
8082849
refactor : Car 객체의 테스트 코드 수정
lxxjn0 Feb 12, 2020
f468499
feat : 자동차의 중복된 이름이 존재하는지 여부를 확인하는 기능 구현
lxxjn0 Feb 12, 2020
991ee17
feat : 자동차의 위치를 문자로 변환하는 기능 구현
lxxjn0 Feb 12, 2020
88058b6
feat : 우승한 위치의 자동차인지 확인하는 기능 구현
lxxjn0 Feb 12, 2020
3211834
docs : README.md에 기능 구현 목록 수정
lxxjn0 Feb 12, 2020
d0d6b27
feat : 우승한 자동차들을 반환해주는 기능 구현
lxxjn0 Feb 12, 2020
11d1fd3
refactor : stringCalculator 패키지 추가 및 StringCalculator 명명 수정
lxxjn0 Feb 12, 2020
e9e5e46
refactor : StringCalculator 테스트 코드 리팩토링
lxxjn0 Feb 12, 2020
e4719be
refactor : racingGame 패키지 명명 수정
lxxjn0 Feb 12, 2020
8b88110
feat : 잘못된 위치를 생성하는지 확인하는 기능 구현
lxxjn0 Feb 12, 2020
8735792
refactor : Position 객체를 추가함으로 인한 전체 코드 리팩토링
lxxjn0 Feb 12, 2020
f7fe364
feat : 입력받은 자동차의 이름을 Cars 객체로 만드는 CarsFactory 구현
lxxjn0 Feb 12, 2020
5b51208
feat : 자동차 이름이 null 또는 공백인지 확인하는 기능 구현
lxxjn0 Feb 12, 2020
6818977
feat : 우승한 자동차의 이름을 이어붙이는 기능 구현
lxxjn0 Feb 13, 2020
6d462c9
feat : 게임을 진행한 후 결과를 반환하는 기능 구현
lxxjn0 Feb 13, 2020
b93e95e
refactor : 게임을 진행한 후 결과를 반환하는 기능 수정
lxxjn0 Feb 13, 2020
6f3992a
feat : 입력과 출력을 담당할 view 객체 생성
lxxjn0 Feb 13, 2020
44d7ae1
feat : 프로그램 동작을 위한 추가적인 클래스 구현
lxxjn0 Feb 13, 2020
c52c332
refactor : 테스트 코드의 반복을 제거하기 위한 리팩토링
lxxjn0 Feb 13, 2020
b25d3ec
refactor : 전체 코드 컨벤션 체크 및 리팩토링 진행
lxxjn0 Feb 13, 2020
44dc6c3
refactor : 역할이 적은 RacingGame 객체를 제거
lxxjn0 Feb 13, 2020
41e5da9
refactor : 1차 피드백 리팩토링
lxxjn0 Feb 15, 2020
740b5b8
refactor : Position의 캐싱 구현
lxxjn0 Feb 15, 2020
6509621
feat : 생성된 랜덤한 수를 감싸는 RandomNo 객체 구현
lxxjn0 Feb 15, 2020
80cc9b8
feat : RandomNo의 캐싱 구현
lxxjn0 Feb 15, 2020
a2d1ad1
refactor : Optional 사용과 결과 출력 부분 수정
lxxjn0 Feb 16, 2020
5a83559
refactor : 2차 피드백 리팩토링
lxxjn0 Feb 16, 2020
44c705d
refactor : 전략 패턴 적용
lxxjn0 Feb 17, 2020
23b32a4
refactor : cars 객체 테스트 코드 수정
lxxjn0 Feb 17, 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
65 changes: 64 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,68 @@
# java-racingcar

자동차 경주 게임 미션 저장소

## 기능 요구사항

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.

- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.

- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.

- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.

- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 -
멈춘다.

- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.

## 기능 구현 목록

- 자동차의 이름을 한 줄로 입력받는 기능

- 입력받은 자동차의 이름을 쉼표(,)로 나누는 기능

- 이름이 5자를 넘지 않는지 확인하는 기능

- 중복된 이름이 존재하는지 여부를 확인하는 기능

- 몇 번의 이동을 할 것인지를 입력받는 기능

- 이동 횟수 입력에 대한 유효성을 검증하는 기능

- 전진하는 조건을 생성하는 기능

- 0에서 9까지의 random 값을 생성하는 기능

- random 값이 4 이상일 경우 전진하는 기능

- random 값이 3 이하인 경우 멈추는 기능

- 주어진 횟수에 따라 자동차를 전진하는 기능

- 자동차가 전진하는 경우 위치를 1 증가시키는 기능

- 여러대의 자동차를 관리하는 기능

- 자동차들을 이동시키는 기능

- 매번 이동이 끝난 후 자동차의 이름과 이동상황을 출력하는 기능

- 우승자를 출력하는 기능

- 자동차가 우승한 위치에 있는지 확인하는 기능

- 우승한 자동차(들)을 반환해주는 기능

- 우승한 자동차(들)을 출력하는 기능

## 기여자

- [라흐]()

- [스티치]()

## 우아한테크코스 코드리뷰
* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)

- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
69 changes: 69 additions & 0 deletions src/main/java/com/woowacourse/racingGame/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.woowacourse.racingGame;

import java.util.ArrayList;
import java.util.List;

import com.woowacourse.racingGame.domain.Cars;
import com.woowacourse.racingGame.domain.CarsFactory;
import com.woowacourse.racingGame.domain.MovementNumber;
import com.woowacourse.racingGame.domain.RandomMovableStrategy;
import com.woowacourse.racingGame.domain.Result;
import com.woowacourse.racingGame.view.InputView;
import com.woowacourse.racingGame.view.OutputView;

public class Application {
private static InputView inputview;
private static OutputView outputView;

public static void main(String[] args) {
inputview = new InputView();
outputView = new OutputView();

final Cars cars = generateCars();
Copy link

Choose a reason for hiding this comment

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

모든 변수에 final을 붙인점이 인상적이네요 👍

Copy link
Author

@lxxjn0 lxxjn0 Feb 15, 2020

Choose a reason for hiding this comment

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

final을 적절한 상황에 맞게 붙여야 한다고 들었는데, 제가 너무 여기저기 다 붙여둔 것이 아닌가 싶은 생각이 들었습니다.

혹시 final을 어떠한 상황에서 붙이는 것이 적절한 지 알려주실 수 있으신가요?
참고할 만한 블로그 링크 정도라도 알려주신다면 정말 감사하겠습니다 👍

Copy link

Choose a reason for hiding this comment

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

아래 글을 읽어보세요~!

결국 Java에서의 final은 Immutable/Read-only 속성을 선언하는 지시어입니다.
클래스, 함수, 변수가 변하지 못하도록 의도하고 싶다면 final로 선언하자.

https://blog.lulab.net/programming-java/java-final-when-should-i-use-it/

final MovementNumber movementNumber = receiveInputMovementNumber();

// NOTE : Log처럼 기록을 출력하는 것이 맞는지 여부 물어보기.
final List<Result> results = playRacingGame(cars, movementNumber);

outputView.printRacingGameResult(results);
outputView.printWinners(getFinalResult(results, movementNumber));
Copy link
Author

Choose a reason for hiding this comment

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

위의 부분에서 매번 경주를 한번 진행할 때마다(자동차 경기 Lap마다) car의 이동 상황을 출력해주는 것이 좋은 방법인가요?
아니면 매번 경주마다의 이동 상황을 제가 구현한 방식처럼 Result라는 기록 저장 객체에 저장해두고 모든 게임이 끝난 후 한번에 출력하는 방법이 좋은 방법인가요?

Copy link

Choose a reason for hiding this comment

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

이 부분은 결과 데이터를 가지고 어떤 기능을 추가적으로 만들 것 인가에 따라 달라질 것 같아요.
따라서 어떤 방법이 더 좋은 방법인가에 대한 답은 요구사항에 따라 다르다라고 생각됩니다.

}

private static Cars generateCars() {
try {
final String carName = inputview.inputCarName();
return CarsFactory.generate(carName, new RandomMovableStrategy());
} catch (IllegalArgumentException e) {
outputView.printExceptionMessage(e.getMessage());
return generateCars();
}
}

private static MovementNumber receiveInputMovementNumber() {
try {
final String movementNumber = inputview.inputMovementNumber();
return new MovementNumber(movementNumber);
} catch (IllegalArgumentException e) {
outputView.printExceptionMessage(e.getMessage());
return receiveInputMovementNumber();
}
}

private static List<Result> playRacingGame(final Cars cars, final MovementNumber movementNumber) {
List<Result> results = new ArrayList<>();

for (int i = 0; i < movementNumber.getMovementNumber(); i++) {
attemptMove(cars);
results.add(new Result(cars));
}
return results;
}

private static void attemptMove(final Cars cars) {
cars.attemptMove();
}

private static Result getFinalResult(final List<Result> results, final MovementNumber movementNumber) {
return results.get(movementNumber.getMovementNumber() - 1);
}
}
55 changes: 55 additions & 0 deletions src/main/java/com/woowacourse/racingGame/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.woowacourse.racingGame.domain;

import java.util.Objects;

public class Car {
private final MovableStrategy movableStrategy;
private final Name name;
private Position position;

public Car(Name name, MovableStrategy movableStrategy) {
this.position = Position.ZERO;
this.name = name;
this.movableStrategy = movableStrategy;
}

Car(Name name, Position position, MovableStrategy movableStrategy) {
this.position = position;
this.name = name;
this.movableStrategy = movableStrategy;
}

public void attemptMoveThrough() {
if (movableStrategy.isMovable()) {
position = position.increaseByMovingUnit();
}
}

// NOTE : 불변객체의 이점?
public boolean isSamePosition(final int winnerPosition) {
return position.getPosition() == winnerPosition;
}
Copy link
Author

Choose a reason for hiding this comment

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

데이브님께서 캐싱을 활용해보라고 했던 Position 객체는 제가 인스턴스 변수가 변하지 않는 불변 객체(? 해당 용어가 맞는 용어인지 정확히 모르겠습니다)를 의도하여 구현했습니다.

그런데 이번 미션에서 보면 이동이 자주 발생할 수 있기 때문에 차라리 Car 객체마다 각각의 Position 객체를 가지고 인스턴스 변수인 int position 을 진행할 때 마다 수정하는 방법이 더 낫지 않았을까? 하는 생각을 해보았습니다.

제가 구현한 것 처럼 인스턴스 변수가 변하지 않도록 구현하는 것이 이번 미션에서 효율적이지 않은 방법일까요?
아니면 이처럼 인스턴스 변수가 변하지 않도록 객체를 구현해야 하는 상황은 어떤 경우에 적절한 지 궁금합니다 🤔

Copy link

Choose a reason for hiding this comment

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

이 글을 읽어보세요.
https://galid1.tistory.com/622


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

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

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Car car = (Car)o;
return name.equals(car.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}
}
54 changes: 54 additions & 0 deletions src/main/java/com/woowacourse/racingGame/domain/Cars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.woowacourse.racingGame.domain;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

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

public Cars(final List<Car> cars) {
checkDuplicate(cars);
this.cars = cars;
}

private void checkDuplicate(final List<Car> cars) {
if (isDuplicateExist(cars)) {
throw new IllegalArgumentException("중복된 이름이 존재합니다.");
}
}

private boolean isDuplicateExist(final List<Car> cars) {
long distinctCarSize = Arrays.stream(cars.toArray())
.distinct()
.count();
return cars.size() != distinctCarSize;
}

public void attemptMove() {
for (Car car : cars) {
car.attemptMoveThrough();
}
}

// NOTE : unmodifiable로 처리해주는 것이 의미를 가지는지 리뷰어님께 여쭤보기.
public List<Car> getCars() {
return Collections.unmodifiableList(cars);
}
Copy link
Author

Choose a reason for hiding this comment

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

제가 공부하기론 일급 컬렉션의 경우 최대한 외부에서 접근할 수 없도록 getter 메소드를 지양하라고 들었습니다.

여기선 출력을 위해서 어쩔 수 없이 getter를 사용하였는데 위처럼Collections.unmodifiableList() 로 처리하는 것이 잘 된 방법인지 궁금합니다.

Copy link

Choose a reason for hiding this comment

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

getter로 받은 원소를 변경 불가능하게 하기 위해 Collections.unmodifiableList()로 처리하는 방법은 좋은 방법이라고 생각합니다.


@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Cars cars = (Cars)o;
return Objects.equals(this.cars, cars.cars);
}

@Override
public int hashCode() {
return Objects.hash(cars);
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/woowacourse/racingGame/domain/CarsFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.woowacourse.racingGame.domain;

import java.util.stream.Collectors;

import com.woowacourse.racingGame.utils.StringUtil;

public class CarsFactory {
public static Cars generate(final String inputCarName, MovableStrategy movableStrategy) {
return new Cars(StringUtil.split(inputCarName).stream()
.map(Name::new)
.map(name -> new Car(name, movableStrategy))
.collect(Collectors.toList()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.woowacourse.racingGame.domain;

public interface MovableStrategy {
boolean isMovable();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.woowacourse.racingGame.domain;

public class MovementNumber {
private static final int ZERO = 0;

private final int movementNumber;

public MovementNumber(final String inputMovementNumber) {
final int parsedNumber = checkValid(inputMovementNumber);
checkPositive(parsedNumber);
this.movementNumber = parsedNumber;
}

private int checkValid(final String number) {
try {
return Integer.parseInt(number);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("입력한 이동 횟수가 숫자가 아닙니다.");
}
}

private void checkPositive(final int parsedNumber) {
if (parsedNumber <= ZERO) {
throw new IllegalArgumentException("입력한 이동 횟수가 0보다 작은 값입니다.");
}
}

public int getMovementNumber() {
return this.movementNumber;
}
}
46 changes: 46 additions & 0 deletions src/main/java/com/woowacourse/racingGame/domain/Name.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.woowacourse.racingGame.domain;

import java.util.Objects;

public class Name {
private static final int MAX_NAME_LENGTH_LIMIT = 5;

private final String name;

public Name(final String name) {
checkNullOrEmpty(name);
checkValidLength(name);
this.name = name;
}

private void checkNullOrEmpty(final String name) {
if (Objects.isNull(name) || name.isEmpty()) {
throw new IllegalArgumentException("null 또는 빈 문자열입니다.");
}
}

private void checkValidLength(final String name) {
if (name.length() > MAX_NAME_LENGTH_LIMIT) {
throw new IllegalArgumentException("이름은 5자이하만 가능합니다.");
}
}

public String getName() {
return name;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Name name = (Name)o;
return Objects.equals(this.name, name.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.woowacourse.racingGame.domain;

public class PlannedMovableStrategy implements MovableStrategy {
private Power power;

public PlannedMovableStrategy(final Power power) {
this.power = power;
}

@Override
public boolean isMovable() {
return power.isMovable();
}
}
Loading