Skip to content

[자동차 경주] 나성 미션 제출합니다. #79

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

Merged
merged 6 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import controller.RaceController;
import view.RaceOutputView;

import java.util.Random;

public class Application {

public static void main(String[] args) {
Random random = new Random();
RaceOutputView raceOutputView = new RaceOutputView();
RaceController raceController = new RaceController(raceOutputView, random);
raceController.start();
}
}
34 changes: 34 additions & 0 deletions src/main/java/controller/RaceController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package controller;

import model.Car;
import model.Racing;
import model.dto.RacingPlayResponse;
import view.RaceOutputView;
import view.UserInputView;

import java.util.Random;

public class RaceController {

private final RaceOutputView raceOutputView;
private final Random random;

public RaceController(RaceOutputView raceOutputView, Random random) {
this.raceOutputView = raceOutputView;
this.random = random;
}

public void start() {
raceOutputView.inputCarNames();
String carNames = UserInputView.readStringInput();
raceOutputView.inputRaceCount();
int raceCount = UserInputView.readIntegerInput();

Racing racing = new Racing(random);

Choose a reason for hiding this comment

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

컨트롤러에서 객체 생성하는 대신, 주입 받는 방법에 대해서 나성님은 어떻게 생각하시나요 ??

개인적으로 컨트롤러의 객체 생성 역할이 애매하다고 생각이 들었고, Racing 객체가 변경되면 컨트롤러에 들어가서 수정을 해야하는 소요가 발생할 것 같습니다 !

Copy link
Author

Choose a reason for hiding this comment

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

Racing은 Service 역할을 하는 VO 객체라고 생각하여 단순하게 객체를 생성하였는데 주입하는 방식이 더 좋은 것 같네요!

RacingPlayResponse response = racing.play(carNames, raceCount);

raceOutputView.printRacingData(response.moveData());
raceOutputView.printWinners(response.winners()
.stream().map(Car::getName).toList());
Comment on lines +30 to +32

Choose a reason for hiding this comment

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

컨트롤러에서 dto의 값을 꺼내서 view로 넘기기 보다는, dto 자체를 넘겨서 view에서 값을 꺼내는 것에 대해서는 어떻게 생각하시나요 ??

컨트롤러에서 dto의 값을 꺼내서 넘겨주면, dto의 의미가 모호해지는 것 같다는 생각이 들었습니다 !

Copy link
Author

Choose a reason for hiding this comment

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

Model -> Controller -> View 를 모두 의존하는 객체를 줄이는 것이 좋다고 생각하였습니다!
DTO 응답으로 필요한 데이터만 보내는 역할을 하는 것도 충분한 DTO의 역할을 한다고 생각합니다.
하지만 이렇게 하면 변환 과정에서 구현 난이도가 더 올라가는 문제점이 있으며 오히려 불필요한 변환 과정이라고도 볼 수 있으므로 View로 DTO를 넘기는 방법도 좋은 것 같습니다~!!

}
}
41 changes: 41 additions & 0 deletions src/main/java/model/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package model;

import java.util.Random;

public class Car {

private final Random random;
private final String name;
private int position = 1;


public Car(String name, Random random) {
this.name = name;
validateCarName(name);
this.random = random;
}

private void validateCarName(String name) {
if(name == null || name.isBlank() || name.length() > 5) {

Choose a reason for hiding this comment

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

나성님은 3가지 조건에 대해서 하나의 예외 메시지로 처리하셨네요 !

5자 이하라는 사람마다 생각하는 부분이 다르겠지만, 누구는 0자도 된다라고 생각하고 입력할 수 있을 것 같아요.

5자 이하인 부분과 입력하지 않는 부분 두 가지로 나눠서 예외 메시지를 처리하면 더 좋은 예외 메시지가 될 것 같은데, 이에 대한 나성님의 생각이 궁금합니다 !

Copy link
Author

Choose a reason for hiding this comment

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

저도 이 부분에서 null이거나 isBlank일 경우 "이름을 입력해주세요." 로 나누려다가
이름 입력 -> 6자 이상 입력 -> 5자 이하 입력으로 사용자 입력이 한 번 더 생기게 유도하지 않을까 생각하였습니다!
오히려 0자 일 때 5자 이하의 이름을 작성해주세요. 라고 메시지를 준다면 바로 5자 이하의 이름을 입력을 하는 동작으로 이어지지 않을까라는 생각이 들어서 한번에 처리하게 된 것 같습니다!
하지만 지금 다시 생각해보면 분리하는 것도 좋은 아이디어이며 isBlank와 length() > 5 검사가 다른 검사이기 때문에 분리하는 것도 더 좋은 로직일 수도 있을 것 같습니다!!
0자와 n자 처리에 대한 사항이 생각보다 자주 있는 것 같은데 고민 해봐야할 것 같습니다!

throw new IllegalArgumentException("5자 이하의 이름을 작성해주세요.");
}
}

public void move() {
if (canMove()) {
position++;
}
}

private boolean canMove() {
return random.nextInt(10) >= 4;
}

public String getName() {
return name;
}

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

import java.util.List;
import java.util.Random;

public class Cars {

private final List<Car> cars;

public Cars(Random random, List<String> carNames) {
this.cars = carNames.stream()
.map(carName -> new Car(carName, random))
.toList();

}

public void allMove(){
cars.forEach(Car::move);
}

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

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

import model.dto.RacingPlayResponse;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Racing {

private final Random rand;

public Racing(Random rand) {
this.rand = rand;
}

public RacingPlayResponse play(String inputCarNames, int raceCount) {
List<String> carNames = Arrays.stream(inputCarNames.split(",")).toList();
Cars cars = new Cars(rand, carNames);
Comment on lines +18 to +19

Choose a reason for hiding this comment

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

join이라는 메소드로 옮기고, 매개변수로 Cars를 받는 것에 대해서 어떻게 생각하시나요 ??

Copy link
Author

Choose a reason for hiding this comment

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

Cars를 만드는 팩토리 클래스를 사용한 후 매개변수로 보내는 것도 좋은 방법인 것 같습니다!

List<Map<String, Integer>> moveData = runRacing(raceCount, cars);

return new RacingPlayResponse(moveData, getWinners(cars));
}

private List<Map<String, Integer>> runRacing(int raceCount, Cars cars) {
List<Map<String, Integer>> moveData = new ArrayList<>();
moveData.add(getSnapshot(cars));
IntStream.range(0, raceCount)
.forEach(i -> moveData.add(moveAndGetSnapshot(cars)));

return moveData;
}

private Map<String, Integer> moveAndGetSnapshot(Cars cars) {
cars.allMove();

return getSnapshot(cars);
}

private Map<String, Integer> getSnapshot(Cars cars) {
return cars.getCars().stream()
.collect(Collectors.toMap(Car::getName, Car::getPosition));
}

private List<Car> getWinners(Cars cars) {
int maxDistance = cars.findMaxDistance();

return cars.getCars().stream()
.filter(car -> car.getPosition() == maxDistance)
.toList();
}
}
12 changes: 12 additions & 0 deletions src/main/java/model/dto/RacingPlayResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package model.dto;

import model.Car;

import java.util.List;
import java.util.Map;

public record RacingPlayResponse(
List<Map<String, Integer>> moveData,
List<Car> winners
) {
}
30 changes: 30 additions & 0 deletions src/main/java/view/RaceOutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package view;

import java.util.List;
import java.util.Map;

public class RaceOutputView {

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

public void inputRaceCount() {
System.out.println("시도할 회수는 몇회인가요?");
}

public void printRacingData(List<Map<String, Integer>> moveData) {
System.out.println("\n실행 결과");
for (Map<String, Integer> data : moveData) {
data.forEach(
(key, value) ->
System.out.println(key + " : " + "-".repeat(value))
);
System.out.println();
}
}

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

import java.util.Scanner;

public class UserInputView {

private static final Scanner scanner = new Scanner(System.in);

public static String readStringInput() {
return scanner.nextLine();
}

public static int readIntegerInput() {
return scanner.nextInt();
}
}
23 changes: 23 additions & 0 deletions src/test/java/common/FakeRandom.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package common;

import java.util.List;
import java.util.Random;

public class FakeRandom extends Random {

private int index;
private final List<Integer> fixedValues;

public FakeRandom(List<Integer> fixedValues) {
this.fixedValues = fixedValues;
}

@Override
public int nextInt(int dummyBound) {

Choose a reason for hiding this comment

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

dummyBound의 역할이 궁금합니다 👀

Copy link
Author

Choose a reason for hiding this comment

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

FakeRandom은 랜덤값을 생성자로 받게 되는데 그렇게 되면 nextInt에서 사용하는 인자값은 필요 없게 됩니다.
(어떤 값이 들어와도 무의미합니다.)
오버라이드할 때 인자를 사용하지 않는다는 뜻을 주기 위해서 dummyBound라는 네이밍을 사용하였습니다!

if(index >= fixedValues.size()) {
throw new IndexOutOfBoundsException();
}

return fixedValues.get(index++);
}
}
52 changes: 52 additions & 0 deletions src/test/java/controller/RaceControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package controller;

import common.FakeRandom;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import view.RaceOutputView;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;

import static fixture.FakeNumber.winnersNumbers;
import static org.assertj.core.api.Assertions.assertThat;

public class RaceControllerTest {

private final RaceOutputView outputView = new RaceOutputView();

@Test
@DisplayName("OK : 자동차 레이싱 플레이")
void testRace_winners() throws IOException {
RaceController raceController = new RaceController(outputView, new FakeRandom(winnersNumbers));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
System.setOut(new PrintStream(outputStream));
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream("neo,brie,brown\n5\n".getBytes());
System.setIn(byteArrayInputStream);

raceController.start();

String output = outputStream.toString();
assertThat(output).contains("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).");
assertThat(output).contains("시도할 회수는 몇회인가요?");
assertThat(output).contains("실행 결과");
assertThat(output).contains("neo : -");
assertThat(output).contains("neo : --");
assertThat(output).contains("neo : ---");
assertThat(output).contains("neo : ----");
assertThat(output).contains("neo : -----");
assertThat(output).contains("brie : -");
assertThat(output).contains("brie : --");
assertThat(output).contains("brie : ---");
assertThat(output).contains("brie : ----");
assertThat(output).contains("brown : -");
assertThat(output).contains("brown : --");
assertThat(output).contains("brown : ---");
assertThat(output).contains("brown : ----");
assertThat(output).contains("brown : -----");
assertThat(output).contains("neo, brown가 최종 우승했습니다.");
byteArrayInputStream.close();
}
}
23 changes: 23 additions & 0 deletions src/test/java/fixture/FakeNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package fixture;

import java.util.List;

public class FakeNumber {

public static final int MOVE_NUMBER = 4;
public static final int STOP_NUMBER = 3;
public static final List<Integer> winnerNumbers = List.of(
MOVE_NUMBER, MOVE_NUMBER, STOP_NUMBER,
STOP_NUMBER, MOVE_NUMBER, STOP_NUMBER,
STOP_NUMBER, MOVE_NUMBER, STOP_NUMBER,
STOP_NUMBER, MOVE_NUMBER, STOP_NUMBER,
STOP_NUMBER, MOVE_NUMBER, STOP_NUMBER
);
public static final List<Integer> winnersNumbers = List.of(
MOVE_NUMBER, STOP_NUMBER, MOVE_NUMBER,
MOVE_NUMBER, MOVE_NUMBER, MOVE_NUMBER,
MOVE_NUMBER, MOVE_NUMBER, MOVE_NUMBER,
MOVE_NUMBER, MOVE_NUMBER, MOVE_NUMBER,
STOP_NUMBER, STOP_NUMBER, STOP_NUMBER
);
}
50 changes: 50 additions & 0 deletions src/test/java/model/CarTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package model;

import common.FakeRandom;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.List;
import java.util.Random;

import static fixture.FakeNumber.MOVE_NUMBER;
import static fixture.FakeNumber.STOP_NUMBER;
import static org.assertj.core.api.Assertions.assertThat;

public class CarTest {

@ParameterizedTest()
@DisplayName("OK : 랜덤값이 4 이상일 시 움직임")
@ValueSource(ints = {MOVE_NUMBER, 9})
void carMove(int fixedValue){
Car car = createCar(fixedValue);
car.move();

assertThat(car.getPosition()).isEqualTo(2);

Choose a reason for hiding this comment

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

자동차의 초기 위치를 1로 설정하신 이유가 있으실까요 ??

뭔가 다른 로직이 있어서 2가 예상 값인가 해서 찾아보다가, 초기 값이 1이더라구요 ㅎㅎ,,

Copy link
Author

Choose a reason for hiding this comment

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

예시에서 처음 상태 출력이 - 로 확인하여서 1을 default 값으로 사용하였습니다!

}

@ParameterizedTest()
@DisplayName("OK : 랜덤값이 4 미만일 시 움직이지 않음")
@ValueSource(ints = {0, STOP_NUMBER})
void carNotMove(int fixedValue){
Car car = createCar(fixedValue);
car.move();

assertThat(car.getPosition()).isEqualTo(1);
}

@Test
@DisplayName("ERROR : 이름 형식이 잘못됨")
void InvalidNameCar(){
Assertions.assertThrows(IllegalArgumentException.class, () -> new Car("", new Random()));
Assertions.assertThrows(IllegalArgumentException.class, () -> new Car(" ", new Random()));
Assertions.assertThrows(IllegalArgumentException.class, () -> new Car("test12", new Random()));
}

private Car createCar(int fixedValue){
return new Car("test1", new FakeRandom(List.of(fixedValue)));
Comment on lines +47 to +48

Choose a reason for hiding this comment

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

파라미터를 바꾸면 FakeRandom를 더 잘 활용할 수 있을 거 같다는 생각이 들었습니다 ! 이렇게 하면, 뭔가 테스트를 더 다양하게 할 수 있을 거 같아요. 가령, A차, B차, C차가 있으면 승자를 예측할 수 있는 테스트도 할 수 있을 거 같아요

카 테스트 보다는 레이싱 테스트에서 사용할 수 있을 거 같아요 !

Suggested change
private Car createCar(int fixedValue){
return new Car("test1", new FakeRandom(List.of(fixedValue)));
private Car createCar(Integer... fixedValue){
return new Car("test1", new FakeRandom(Arrays.asList(fixedValue)));

Copy link
Author

Choose a reason for hiding this comment

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

그렇게 한다면 Fake 객체를 더 다양하고 잘 사용할 수 있을 것 같아요!!

}
}
Loading