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

Merged
merged 33 commits into from
Dec 5, 2019
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
250dc42
docs: 간단한 설계 작성
dpudpu Nov 16, 2019
1f1b185
feat: Name VO
dpudpu Nov 16, 2019
33bceb2
feat: Position VO
dpudpu Nov 16, 2019
bcbe5b5
feat: Car
dpudpu Nov 16, 2019
42111ac
feat: Cars 문자열로 List<Car> 생성 구현
dpudpu Nov 16, 2019
81b6874
refactor: MoveStrategy.isAvailableMove() 파라미터 추가
dpudpu Nov 16, 2019
7ef12bb
feat: Cars 이동 및 우승 구하기
dpudpu Nov 16, 2019
e538057
feat: Cars 이름 2명 미만 입력시 예외처리
dpudpu Nov 16, 2019
6b0ff66
refactor: Car, Cars move() -> shouldMove()로 이름 변경
dpudpu Nov 17, 2019
1323fbe
feat: RepeatNumber (시도할 회수값 포장)
dpudpu Nov 17, 2019
b738501
refactor: Cars.shouldMove() 리턴타입 void -> List<Car> 변경
dpudpu Nov 17, 2019
f45db21
feat: RaceStatus, CarDto
dpudpu Nov 17, 2019
48d6c6e
refactor: Cars.getWinners() 제거 -> RaceStatus 로 위임
dpudpu Nov 17, 2019
5aa31ed
feat: RaceResult
dpudpu Nov 17, 2019
7f9766b
feat: RaceResult Iterator 구현
dpudpu Nov 17, 2019
5061815
feat: RacingService
dpudpu Nov 17, 2019
0dd39e3
feat: RandomNumberMoveStrategy
dpudpu Nov 17, 2019
0942191
feat: View, Main 메소드 구현
dpudpu Nov 17, 2019
531f355
style: 포맷팅
dpudpu Nov 17, 2019
5d58965
refactor: OutputView 메소드 추출
dpudpu Nov 17, 2019
b083a2b
refactor: domain 패키지 세분화
dpudpu Nov 17, 2019
7a452ed
feat: RaceStatus Empty 검사 추가
dpudpu Nov 17, 2019
6e81dd1
feat: Name 공백처리 추가
dpudpu Nov 17, 2019
83db448
refactor: 상수와 에러메시지 연동
dpudpu Nov 17, 2019
4afecb7
feat: Position 캐싱 추가
dpudpu Nov 17, 2019
24b7e46
refactor: Test코드 매직넘버 프로덕션 코드의 상수로 변경
dpudpu Nov 18, 2019
cc83425
test: CarsTest 이름 정상 입력 테스트 추가
dpudpu Nov 18, 2019
2fb4281
refactor: Car, Cars 의 shouldMove() -> tryMove() 이름 변경
dpudpu Nov 18, 2019
65b3ba2
feat: Name null 검사 추가
dpudpu Nov 20, 2019
b0ca4ab
feat: RepeatNumber null 검사 추가
dpudpu Nov 20, 2019
ec4a347
style: Position 공백
dpudpu Nov 20, 2019
afb53fe
feat: 예외 발생시 사용자에게 메시지 표시
dpudpu Nov 20, 2019
edd5e30
refactor: CarDto 제거 -> Car로 대체
dpudpu Nov 20, 2019
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
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,34 @@





## 목표

- 무조건 TDD
- 자바8 활용 (for문 사용 x)
- SOLID 원칙 지키기.
- 응집도 높게
- 불변으로



## 간단한 설계

- Car
- 4 이상 전진, 3 이하 정지
- Name
- 2자 이상, 5자 이하 가능
- Position
- 기본값 1
- Cars
- `,` 기준으로 분리 후 `List<Car>` 생성

- MoveStrategy
- RandomeMoveStrategy



## 객체지향 생활 체조

- 규칙 1: 한 메서드에 오직 한 단계의 들여쓰기만 한다.
Expand All @@ -13,4 +41,5 @@
- 규칙 6: 모든 엔티티를 작게 유지한다.
- 규칙 7: 2개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
- 규칙 8: 일급 콜렉션을 쓴다.
- 규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.
- 규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.

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

import racingcar.console.view.InputView;
import racingcar.console.view.OutputView;
import racingcar.domain.car.Cars;
import racingcar.domain.movestrategy.MoveStrategy;
import racingcar.domain.movestrategy.RandomNumberMoveStrategy;
import racingcar.domain.race.RaceResult;
import racingcar.service.RacingService;

public class App {
public static void main(String[] args) {
final MoveStrategy moveStrategy = new RandomNumberMoveStrategy();
final RacingService racingService = new RacingService(moveStrategy);

final String names = InputView.inputNames();
final Cars cars = racingService.createCars(names);

final int repeatNumber = InputView.inputRepeatNumber();
final RaceResult raceResult = racingService.startRace(repeatNumber, cars);
Copy link

Choose a reason for hiding this comment

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

실행 결과 예외 상황이 조금 더 있을 수도 있을 것 같은데요.
제가 조금 빡빡하게 예외를 체크하는 편이라 그렇지만

  1. 중복된 자동차 이름이 있을 경우
  2. red, blue, green 이런 식으로 공백을 포함하여 사용자가 이름을 입력하는 경우

이런 경우에 대해서는 어떻게 생각하세요?
크게 동의하지 않으시면 넘어가주셔도 좋습니다 :)

Copy link

Choose a reason for hiding this comment

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

그리고 현재 예외가 발생하면 그냥 런타임 에러가 콘솔에 표시되면서 터지는데요.
에러 로그를 날것으로 보여주는 것보다는
try-catch로 잡아서 OutputView를 통해 예외 상황을 사용자에게 알려주는 것은 어떨까 제안드려 봅니다 :)

Copy link
Member Author

@dpudpu dpudpu Nov 17, 2019

Choose a reason for hiding this comment

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

1.중복된 자동차 이름이 있을 경우 일부러 허용해줬습니다.
2. red, blue, green 공백은 오랜만에 해서 감을 잃었는지 미처 생각을 못했네요.
3. 제가 예외처리에 소홀했네요.

좋은 피드백 감사합니다 ㅎㅎ


OutputView.printRaceResult(raceResult);
OutputView.printWinner(raceResult.getWinners());
}
}
20 changes: 20 additions & 0 deletions src/main/java/racingcar/console/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package racingcar.console.view;

import java.util.Scanner;

public class InputView {
public static final Scanner scanner = new Scanner(System.in);

private InputView() {
}
Comment on lines +8 to +9

Choose a reason for hiding this comment

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

private 기본 생성자를 써준 이유를 알 수 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

모든 메소드가 static 이여서 인스턴스를 생성할 필요가 없습니다. 이를 방지하기 위해서 private으로 해줬습니다.


public static String inputNames() {
System.out.println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)");
return scanner.nextLine();
}

public static int inputRepeatNumber() {
System.out.println("시도할 회수는 몇회인가요?");
return scanner.nextInt();
}
}
46 changes: 46 additions & 0 deletions src/main/java/racingcar/console/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package racingcar.console.view;

import racingcar.domain.race.RaceResult;
import racingcar.service.dto.CarDto;

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

public class OutputView {

private static final String ROUTE = "-";

private OutputView() {
}

public static void printRaceResult(final RaceResult raceResult) {
StringBuilder sb = new StringBuilder();
sb.append("실행 결과\n");
while (raceResult.hasNext()) {
printCurrentRace(raceResult.next(), sb);
}
System.out.println(sb.toString());
}

private static void printCurrentRace(final List<CarDto> carDtos, final StringBuilder sb) {
carDtos.forEach(carDto -> printCar(sb, carDto));
sb.append("\n");
}

private static void printCar(final StringBuilder sb, final CarDto carDto) {
sb.append(String.format("%s : ", carDto.getName()));
for (int i = 0; i < carDto.getPosition(); i++) {
sb.append(ROUTE);
}
sb.append("\n");
}


public static void printWinner(final List<CarDto> winners) {
final String joinedWinners = winners.stream()
.map(CarDto::getName)
.collect(Collectors.joining(", "));

Choose a reason for hiding this comment

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

joining 사용 👍


System.out.println(String.format("%s가 최종 우승했습니다.", joinedWinners));
}
}
39 changes: 39 additions & 0 deletions src/main/java/racingcar/domain/car/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package racingcar.domain.car;

import racingcar.domain.movestrategy.MoveStrategy;

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

Choose a reason for hiding this comment

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

포장 👍


public Car(final String name) {
this.name = Name.of(name);
this.position = Position.newInstance();
}

public static Car of(final String name) {
return new Car(name);
}

void tryMove(final MoveStrategy moveStrategy) {
if (moveStrategy.isAvailableMove(this)) {
position = position.increase();
}
}

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

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

@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", position=" + position +
'}';
}
}
37 changes: 37 additions & 0 deletions src/main/java/racingcar/domain/car/Cars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package racingcar.domain.car;

import racingcar.domain.movestrategy.MoveStrategy;

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

public class Cars {
public static final int MINIMUM_NAMES = 2;
private static final String DELIMITER = ",";

private final List<Car> cars;

public Cars(final String names) {
this.cars = createCars(names);
validateCarSize();
}

private void validateCarSize() {
if (cars.size() < MINIMUM_NAMES) {

Choose a reason for hiding this comment

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

저는 if 조건을 메서드화 시키는게 좋다고 생각하는데,
어떻게 생각하시나요?
예를들어 위 cars.size() < MINIMUM_NAMES 같은 경우엔
isInvalidCarSize 라던지...

throw new IllegalArgumentException(MINIMUM_NAMES + "명 이상 입력해주세요.");
}
}

private List<Car> createCars(final String names) {
return Stream.of(names.split(DELIMITER))
.map(Car::new)
.collect(Collectors.toList());
}

public List<Car> tryMove(final MoveStrategy moveStrategy) {
cars.forEach(car -> car.tryMove(moveStrategy));
return Collections.unmodifiableList(cars);
}
}
47 changes: 47 additions & 0 deletions src/main/java/racingcar/domain/car/Name.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package racingcar.domain.car;

import java.util.Objects;

public class Name {
private static final int MAX_BOUNDARY = 5;
private static final int MIN_BOUNDARY = 2;

private final String name;

private Name(final String name) {
this.name = name.trim();
validateLengthOfName();
}

private void validateLengthOfName() {
if (this.name.length() > MAX_BOUNDARY || this.name.length() < MIN_BOUNDARY) {

Choose a reason for hiding this comment

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

여기에도 아까 말씀 드렸듯이 if 조건을 메서드로 분리 시키면 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

메소드가 복잡하면 좋은 방법이라고 생각하지만, 현재는 이 로직밖에 없는데 또 분리시키면 복잡하지 않을까요? 현재 메소드명으로 충분히 설명이 가능하다고 생각해요

throw new IllegalArgumentException(String.format("이름은 %d~%d자로 해주세요.", MIN_BOUNDARY, MAX_BOUNDARY));
}
}

static Name of(final String name) {
return new Name(name);
}

String getName() {
return name;
}

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

@Override
public int hashCode() {
return Objects.hash(name);
}

@Override
public String toString() {
return "name= " + name;
}
}
49 changes: 49 additions & 0 deletions src/main/java/racingcar/domain/car/Position.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package racingcar.domain.car;

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

public class Position {
private static final int DEFAULT_CACHE_SIZE = 30;
static final int UNIT_INCREASE = 1;
static final int DEFAULT_POSITION = 0;

private static final Map<Integer, Position> CACHE = new HashMap<>();


static {
for (int i = DEFAULT_POSITION; i < DEFAULT_CACHE_SIZE; i += UNIT_INCREASE) {
CACHE.put(i, new Position(i));
}
}

private final int position;

private Position(final int position) {
this.position = position;
}

static Position newInstance() {
return CACHE.get(DEFAULT_POSITION);
}

Position increase() {
final int increasedPosition = position + UNIT_INCREASE;
return CACHE.getOrDefault(increasedPosition, create(increasedPosition));
}

private Position create(final int position) {
final Position createdPosition = new Position(position);
CACHE.put(position, createdPosition);
return createdPosition;
}

int getPosition() {
return position;
}

@Override
public String toString() {
return "position= " + position;
}
}
27 changes: 27 additions & 0 deletions src/main/java/racingcar/domain/common/RepeatNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package racingcar.domain.common;

public class RepeatNumber {
private static final int NUMBER_BOUNDARY = 1;

private final int number;

public RepeatNumber(final int number) {
if (number < NUMBER_BOUNDARY) {
throw new IllegalArgumentException(NUMBER_BOUNDARY + " 이상 입력해주세요.");
}
this.number = number;
}

public static RepeatNumber from(final int number) {
return new RepeatNumber(number);
}


public static RepeatNumber from(final String number) {
return new RepeatNumber(Integer.parseInt(number));
}

public int getNumber() {
return number;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar.domain.exception;

import racingcar.domain.car.Cars;

public class RaceStatusEmptyException extends IllegalStateException {
public RaceStatusEmptyException() {
super("CarDto가 비어 있습니다." + Cars.MINIMUM_NAMES + "명 이상이어야 합니다.");
}
}
7 changes: 7 additions & 0 deletions src/main/java/racingcar/domain/movestrategy/MoveStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package racingcar.domain.movestrategy;

import racingcar.domain.car.Car;

public interface MoveStrategy {
boolean isAvailableMove(final Car car);
Copy link

@Deocksoo Deocksoo Nov 18, 2019

Choose a reason for hiding this comment

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

메서드 인자로 car를 넣어주는 이유가 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

처음엔 테스트를 수월하게 하기 위해서 넣었습니다. (car의 이름이 pobi일 때만 true) 이런식으로 하기 위해서요. 하지만 테스트를 위한 코드라서 다시 수정 하려다가 나중에 경우에 따라서 car가 필요할 수 도 있겠다 싶어서 유지했습니다.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package racingcar.domain.movestrategy;

import racingcar.domain.car.Car;

import java.util.Random;

public class RandomNumberMoveStrategy implements MoveStrategy {
private static final int MOVE_BOUNDARY = 4;
private static final int RANDOM_MAX_BOUNDARY = 9;

private final Random random = new Random();

@Override
public boolean isAvailableMove(final Car car) {
return random.nextInt(RANDOM_MAX_BOUNDARY + 1) >= MOVE_BOUNDARY;
}
}
Loading