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단계 - 자동차 경주 구현] 프린(남기범) 미션 제출합니다. #668

Merged
merged 50 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
eaa23a9
docs: 기능 목록 ver 1.0
GIVEN53 Feb 14, 2024
788c709
feat: 이름 클래스 추가
GIVEN53 Feb 14, 2024
00c9f90
feat: 시도 횟수 클래스 추가
GIVEN53 Feb 14, 2024
cc21e68
feat: 시도 횟수를 1 감소시키는 기능 추가
GIVEN53 Feb 14, 2024
054d28e
feat: 시도 횟수가 0인지 확인하는 기능 추가
GIVEN53 Feb 14, 2024
104bb98
test: 이름 길이에 따른 예외 발생 테스트
GIVEN53 Feb 14, 2024
570a5bc
fix: 메서드명 의미를 명확하게 수정
GIVEN53 Feb 14, 2024
68683e1
fix: 시도횟수가 0일 때 예외를 던지도록 수정
GIVEN53 Feb 14, 2024
05bc950
test: 시도 횟수 메서드 테스트
GIVEN53 Feb 14, 2024
8db9152
fix: final 키워드 추가해서 불변하도록 변경
GIVEN53 Feb 14, 2024
40b2e67
feat: 자동차 클래스 추가
GIVEN53 Feb 14, 2024
4180da2
feat: 랜덤 숫자 생성하는 클래스 추가
GIVEN53 Feb 14, 2024
b702ab8
test: 랜덤으로 생성한 숫자가 범위 이내의 값을 가지는지 테스트
GIVEN53 Feb 14, 2024
ccd00ac
feat: 자동차 일급 컬렉션 추가
GIVEN53 Feb 14, 2024
9b95a13
test: 자동차 일급컬렉션 테스트
GIVEN53 Feb 14, 2024
f2316ec
feat: 자동차 이름 입력 및 시도 횟수 입력받는 클래스 추가
GIVEN53 Feb 14, 2024
ea6e6c3
refactor: position 초기화 제거, 메서드명 수정
GIVEN53 Feb 15, 2024
22259fc
feat: 자동차를 움직이는 기능 구현
GIVEN53 Feb 15, 2024
ce4056d
feat: 우승자 판별 구현
GIVEN53 Feb 15, 2024
92e0150
feat: 전진 결과를 출력하는 기능 추가
GIVEN53 Feb 15, 2024
917e0b0
feat: 우승자를 출력하는 기능 추가
GIVEN53 Feb 15, 2024
9fdd000
feat: 레이스를 시작하는 기능 추가
GIVEN53 Feb 15, 2024
2e92cf2
feat: 의존성 주입하여 메인 실행
GIVEN53 Feb 15, 2024
2fb111d
refactor: 디렉토리 분리, 이동
GIVEN53 Feb 15, 2024
6ffd25a
fix: 동등성 비교를 위한 메서드 오버라이딩 추가
GIVEN53 Feb 15, 2024
1cb4aa1
feat: 자동차 목 객체 생성 fixture
GIVEN53 Feb 15, 2024
b57f517
test: 자동차 테스트 추가
GIVEN53 Feb 15, 2024
30d6ae6
refactor: fixture 반영
GIVEN53 Feb 15, 2024
d433dcf
refactor: 우승자 반환 로직을 레이스에서 Cars로 이동
GIVEN53 Feb 15, 2024
4863d7c
test: 우승자 반환하는 함수 테스트
GIVEN53 Feb 15, 2024
2eda3b7
refactor: 최소, 최대횟수 상수화
GIVEN53 Feb 15, 2024
e6a0a93
refactor: 매직넘버 상수화
GIVEN53 Feb 15, 2024
4aeab79
refactor: 생성자 private으로 제한
GIVEN53 Feb 15, 2024
50e8c2f
style: 개행 제거
GIVEN53 Feb 15, 2024
14bcb70
refactor: race controller, service 분리
GIVEN53 Feb 15, 2024
9905671
refactor: 미구현 테스트 삭제
GIVEN53 Feb 15, 2024
588c6b6
refactor: input, output view의 static 삭제
GIVEN53 Feb 15, 2024
2e585d3
refactor: delimiter enum으로 분리
GIVEN53 Feb 15, 2024
514a45f
refactor: error message를 string format으로 변경
GIVEN53 Feb 15, 2024
fd044fc
refactor: 한글, 숫자도 가능하도록 변경
GIVEN53 Feb 15, 2024
3f6a64c
feat: 입력값에 대한 검증 기능 추가
GIVEN53 Feb 15, 2024
299e9b5
test: 입력값의 공통된 검증 기능 테스트
GIVEN53 Feb 15, 2024
3d1c595
test: 자동차 이름 입력값 검증 기능 테스트
GIVEN53 Feb 15, 2024
2ab8cdb
test: 시도 횟수 입력값 검증 기능 테스트
GIVEN53 Feb 15, 2024
85f4d34
style: import 코드 스타일 적용
GIVEN53 Feb 15, 2024
cc143b7
test: 자동차가 전진 임계값에 따라 전진하는지 테스트
GIVEN53 Feb 15, 2024
5da3592
refactor: 우승자를 찾는 기능 분리
GIVEN53 Feb 15, 2024
0d58862
feat: 자동차 fixture 추가
GIVEN53 Feb 15, 2024
3eb4d45
test: 우승자를 찾는 기능 테스트
GIVEN53 Feb 15, 2024
4c4845b
test: 가장 많이 전진한 자동차의 위치를 반환하는지 테스트
GIVEN53 Feb 15, 2024
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: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,34 @@
## 우아한테크코스 코드리뷰

- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)

## 요구사항

Choose a reason for hiding this comment

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

👍
기능별로 깔끔하게 잘 정리되었네요

### 자동차
- [x] 앞으로 전진한다.
- [x] 자동차 이름이 중복인지 확인한다.

### 이름
- [x] 1 ~ 5자 사이인지 확인한다.

### 시도 횟수
- [x] 1 ~ 120 이상의 숫자인지 확인한다.
- [x] 시도 횟수 1 감소한다.
- [x] 시도 횟수가 0인지 확인한다.

### 레이스
- [x] 레이스를 시작한다.
- [x] 숫자가 4 이상이면 자동차를 전진시키고, 3 이하면 멈춘다.
- [x] 전진 횟수에 따라 우승자를 판단한다.

### 랜덤 숫자 생성
- [x] 0 ~ 9 사이의 랜덤 숫자를 생성한다.

### 결과
- [x] 전진 결과를 출력한다.
- [x] 우승자를 출력한다.

### 입력
- [x] 자동차 이름들을 입력받는다.
- [x] 쉼표(,)로 구분되어 있는지 확인한다.
- [x] 시도 횟수를 입력받는다.
- [x] 숫자인지 확인한다.
17 changes: 17 additions & 0 deletions src/main/java/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import application.RaceService;
import controller.RaceController;
import ui.InputView;
import ui.OutputView;
import util.CarNamesValidator;
import util.RandomNumberGenerator;
import util.TryCountValidator;

public class Main {
public static void main(String[] args) {
final InputView inputView = new InputView(new CarNamesValidator(), new TryCountValidator());
final OutputView outputView = new OutputView();
final RaceService raceService = new RaceService(new RandomNumberGenerator());
final RaceController raceController = new RaceController(inputView, outputView, raceService);
raceController.start();
}
}
33 changes: 33 additions & 0 deletions src/main/java/application/RaceService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package application;

import java.util.List;
import model.Car;
import model.Cars;
import util.NumberGenerator;

public class RaceService {
private static final int MOVE_THRESHOLD = 4;
private final NumberGenerator numberGenerator;

public RaceService(NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
}

public void moveCars(Cars cars) {
cars.getCars()
Copy link

@Deocksoo Deocksoo Feb 15, 2024

Choose a reason for hiding this comment

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

8칸 indent를 쓰시는군요?!
4칸 indent 설정에 대해서 어떻게 생각하시나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

엇 우테코 코드 스타일을 설정해서 쓰고 있는데 적용이 안된걸까요??
스크린샷 2024-02-15 오후 11 36 10

Choose a reason for hiding this comment

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

아하 ㅎㅎ 우테코 코드 스타일이었군요
잘 적용된 것 같고, 그대로 사용해주시는게 좋을것같아요.
제 개인적인 선호를 잘못 말씀드렸네요 :)

.forEach(car -> {
int number = numberGenerator.generateNumber();
if (number >= MOVE_THRESHOLD) {
car.moveForward();
}
});
}

public List<String> findWinners(Cars cars) {

Choose a reason for hiding this comment

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

cars.findMaxPosition 기능은 일급컬렉션인 cars 가 맡도록 하셨군요 :)
findWinners 메서드도 마찬가지로 cars가 맡도록 하면 어떤가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

cars가 맡고 있다가 우승자는 서비스에서 판단하는게 맞지 않나? 싶어서 다시 바꾸게 되었는데 cars가 하는게 더 좋을 것 같네요 행위를 한 곳에서 관리할 수 있는 일급 컬렉션의 장점도 살릴 수 있구요! 감사합니다😁

int maxPosition = cars.findMaxPosition();
return cars.getCars().stream()
.filter(car -> car.isSamePosition(maxPosition))
.map(Car::getName)
.toList();
}
}
52 changes: 52 additions & 0 deletions src/main/java/controller/RaceController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package controller;

import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;

import application.RaceService;
import java.util.Arrays;
import java.util.List;
import model.Car;
import model.Cars;
import model.Name;
import model.TryCount;
import ui.InputView;
import ui.OutputView;

public class RaceController {
private final InputView inputView;
private final OutputView outputView;
private final RaceService raceService;

public RaceController(InputView inputView, OutputView outputView, RaceService raceService) {
this.inputView = inputView;
this.outputView = outputView;
this.raceService = raceService;
}

public void start() {
Cars cars = createCars();
TryCount tryCount = createTryCount();
outputView.printResultHeader();
while (tryCount.hasTryCount()) {
raceService.moveCars(cars);
outputView.printCarNameAndPosition(cars);
tryCount.decreaseTryCount();
}
List<String> winners = raceService.findWinners(cars);
outputView.printWinners(winners);
}

private Cars createCars() {
String[] carNames = inputView.inputCarNames();
return Arrays.stream(carNames)
.map(Name::new)
.map(Car::new)
.collect(collectingAndThen(toList(), Cars::new));

Choose a reason for hiding this comment

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

오 이렇게도 묶을 수 있군요 ㅎㅎ 👍

}

private TryCount createTryCount() {
int tryCount = inputView.inputTryCount();
return new TryCount(tryCount);
}
}
27 changes: 27 additions & 0 deletions src/main/java/enums/Delimiter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package enums;

public enum Delimiter {

Choose a reason for hiding this comment

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

Enum을 유용한 방식으로 사용하셨네요 👍

COMMA(",", "^[a-zA-Z가-힣\\d]+(,[a-zA-Z가-힣\\d]+)*$", "쉼표");

private final String value;
private final String regex;
private final String korName;

Delimiter(String value, String regex, String korName) {
this.value = value;
this.regex = regex;
this.korName = korName;
}

public String getValue() {
return this.value;
}

public String getRegex() {
return this.regex;
}

public String getKorName() {
return this.korName;
}
}
42 changes: 42 additions & 0 deletions src/main/java/model/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package model;

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

public Car(Name name) {
this.name = name;
}

public void moveForward() {
this.position++;
}

public boolean isSamePosition(int position) {
return this.position == position;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Car car) {
return this.name.equals(car.name);
}
return false;
}

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

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

public int getPosition() {
return this.position;
}
}
35 changes: 35 additions & 0 deletions src/main/java/model/Cars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package model;

import java.util.Collections;
import java.util.List;

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

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

private void verifyDuplicateCarNames(List<Car> cars) {
long distinctCount = cars.stream()
.map(Car::getName)
.distinct()
.count();

if (distinctCount != cars.size()) {
throw new IllegalArgumentException("중복된 이름의 자동차가 존재합니다.");
}
}

public int findMaxPosition() {
return cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElse(0);
}

public List<Car> getCars() {
return Collections.unmodifiableList(cars);
}
}
39 changes: 39 additions & 0 deletions src/main/java/model/Name.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package model;

public class Name {
private static final int MIN_NAME_LENGTH = 1;
private static final int MAX_NAME_LENGTH = 5;
private final String name;

public Name(String name) {
verifyNameLength(name);
this.name = name;
}

private void verifyNameLength(String name) {
if (name.length() < MIN_NAME_LENGTH || name.length() > MAX_NAME_LENGTH) {
throw new IllegalArgumentException(
String.format("이름은 %d자 이상 %d자 이하여야 합니다.", MIN_NAME_LENGTH, MAX_NAME_LENGTH));
}
}

protected String getName() {
return name;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Name name) {
return this.name.equals(name.name);
}
return false;
}

@Override
public int hashCode() {
return this.name.hashCode();
}
}
30 changes: 30 additions & 0 deletions src/main/java/model/TryCount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package model;

public class TryCount {
private static final int MIN_TRY_COUNT = 1;
private static final int MAX_TRY_COUNT = 120;
private int tryCount;

public TryCount(int tryCount) {
verifyTryCount(tryCount);
this.tryCount = tryCount;
}

private void verifyTryCount(int tryCount) {
if (tryCount < MIN_TRY_COUNT || tryCount > MAX_TRY_COUNT) {
throw new IllegalArgumentException(
String.format("시도 횟수는 %d 이상 %d 이하여야 합니다.", MIN_TRY_COUNT, MAX_TRY_COUNT));
}
}

public void decreaseTryCount() {
if (tryCount == 0) {
throw new IllegalStateException("시도 횟수가 모두 소진되었습니다.");
}
tryCount--;
}

public boolean hasTryCount() {
return tryCount > 0;
}
}
31 changes: 31 additions & 0 deletions src/main/java/ui/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ui;

import static enums.Delimiter.COMMA;

import java.util.Scanner;
import util.InputValidator;

public class InputView {
private final Scanner scanner = new Scanner(System.in);
private final InputValidator carNamesValidator;
private final InputValidator tryCountValidator;

public InputView(InputValidator carNamesValidator, InputValidator tryCountValidator) {
this.carNamesValidator = carNamesValidator;
this.tryCountValidator = tryCountValidator;
}

public String[] inputCarNames() {
System.out.printf("경주할 자동차 이름을 입력하세요(이름은 %s(%s) 기준으로 구분).%n", COMMA.getKorName(), COMMA.getValue());
String carNames = scanner.nextLine();
carNamesValidator.validate(carNames);
return carNames.split(COMMA.getValue());
}

public int inputTryCount() {
System.out.println("시도할 회수는 몇 회인가요?");
String tryCount = scanner.nextLine();
tryCountValidator.validate(tryCount);
return Integer.parseInt(tryCount);
}
}
25 changes: 25 additions & 0 deletions src/main/java/ui/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ui;

import static enums.Delimiter.COMMA;

import java.util.List;
import model.Cars;

public class OutputView {
public void printResultHeader() {
System.out.println();
System.out.println("실행 결과");
}

public void printCarNameAndPosition(Cars cars) {
cars.getCars()
.forEach(car ->
System.out.printf("%s : %s%n", car.getName(), "-".repeat(car.getPosition()))
);
System.out.println();
}

public void printWinners(List<String> winners) {
System.out.println(String.join(COMMA.getValue() + " ", winners) + "가 최종 우승했습니다.");
}
}
17 changes: 17 additions & 0 deletions src/main/java/util/CarNamesValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package util;

import static enums.Delimiter.COMMA;

import java.util.regex.Pattern;

public class CarNamesValidator extends InputValidator {
private static final Pattern CAR_NAMES_PATTERN = Pattern.compile(COMMA.getRegex());

@Override
void validateCustom(String input) {
if (!CAR_NAMES_PATTERN.matcher(input).matches()) {
throw new IllegalArgumentException(
String.format("자동차 이름은 %s(%s)를 기준으로 구분해야 합니다.", COMMA.getKorName(), COMMA.getValue()));
}
}
}
Loading