-
Notifications
You must be signed in to change notification settings - Fork 94
[최보현] 자동차 경주 미션 제출 #133
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
base: boya-go
Are you sure you want to change the base?
[최보현] 자동차 경주 미션 제출 #133
Changes from 13 commits
04e2c45
884845c
72129c3
34fcac4
fcaa1d5
0c97bd5
933f644
3a8ff9b
bbb7480
8ebd4ee
27b8519
1dd8c56
f21a690
51cd513
960b262
f5fe592
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# 자동차 경주 - 초간단 애플리케이션 | ||
|
||
## [1] 움직이는 자동차 | ||
|
||
- 자동차에 관한 요구사항 | ||
- [x] 자동차는 이름을 가지고 있다. | ||
- [x] 자동차는 움직일 수 있다. | ||
- [x] 0에서 9 사이의 random 값을 구해 그 값이 **4 이상이면 전진**하고, **3 이하 값이면 멈춘다**. | ||
|
||
--- | ||
|
||
## [2] 우승 자동차 구하기 | ||
|
||
- 기능 요구사항 | ||
- [x] n대의 자동차 참여 가능 | ||
- [x] 주어진 횟수동안 n대의 자동차는 전진 또는 멈출 수 있다. | ||
- [x] 이동 조건은 앞의 [움직이는 자동차]와 동일 | ||
- [x] 경주 게임 완료 후 우승자를 구할 수 있다. | ||
- [x] 우승자는 한 명 이상일 수 있다. | ||
|
||
--- | ||
|
||
## [3] 게임 실행 | ||
|
||
- 기능 요구사항 | ||
- [x] 자동차 및 게임 방법은 앞의 [움직이는 자동차], [우승 자동차 구하기]와 동일 | ||
- [x] 사용자로부터 `이름`, `게임횟수` 를 입력받는다. | ||
- [x] 자동차 이름은 쉼표(,) 기준으로 구분하며, 이름은 5자 이하만 가능하다. | ||
- [x] 메인 메서드를 추가하여 실행 가능하게 만든다. | ||
|
||
--- | ||
|
||
## [4] 리팩터링 | ||
|
||
- 요구사항 | ||
- [x] 단위 테스트를 구현한다. | ||
|
||
--- | ||
|
||
- 코드 작성 요구사항 | ||
- [x] 자동차가 움직이는 기능이 의도대로 되는지 테스트한다. | ||
- [x] 자바 코드 컨벤션 `Java Style Guide` 원칙으로 프로그래밍한다. | ||
- [x] 3항 연산자를 쓰지 않는다. | ||
- [x] else 예약어를 쓰지 않는다. | ||
- [x] switch/case도 사용하지 않는다. | ||
- [x] 함수(또는 메서드) 길이가 15라인을 넘어가지 않도록 구현한다. | ||
- [x] 함수(또는 메서드)가 한 가지 일만 하도록 한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import controller.RacingGameController; | ||
|
||
public class App { | ||
public static void main(String[] args) { | ||
RacingGameController racingGameController = new RacingGameController(); | ||
|
||
racingGameController.startRacingGame(); | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package controller; | ||
|
||
import domain.Car; | ||
import domain.CarRaceGame; | ||
import domain.Generator.RandomNumberGenerator; | ||
import view.inputView; | ||
import view.outputView; | ||
|
||
import java.util.*; | ||
import java.util.stream.Collectors; | ||
|
||
public class RacingGameController { | ||
|
||
public static final String DELIMITER = ","; | ||
|
||
public void startRacingGame(){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 함수(또는 메서드) 길이가 15라인을 넘어가지 않도록 구현한다!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
String names = inputView.enterCarNames(); | ||
|
||
List<Car> cars = Arrays.stream(names.split(DELIMITER)) | ||
.map(name -> new Car(name, new RandomNumberGenerator())) | ||
.collect(Collectors.toList()); | ||
|
||
final CarRaceGame carRaceGame = new CarRaceGame(cars); | ||
|
||
int round = inputView.enterRoundNumber(); | ||
|
||
carRaceGame.validateRoundNumber(round); | ||
|
||
outputView.printGameResultTitle(); | ||
|
||
for (int i = 0; i < round; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Controller에서 round를 사용하고 CarRaceGame에서는 round를 검증하고 있는데요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. round 변수 사용 이전에 검증하면 된다고 생각했습니다. round 역시 Car 객체의 name과 마찬가지로 검증 후 할당하게끔 하고 싶은데 어떤 식으로 해야할지 잘 모르겠습니다 🥲 |
||
|
||
carRaceGame.playOneRound(); | ||
System.out.println(); | ||
outputView.printRoundResult(cars); | ||
|
||
} | ||
|
||
List<String> winnersName = carRaceGame.getWinnerNames(); | ||
|
||
outputView.printGameWinners(winnersName); | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package domain; | ||
|
||
import domain.Generator.NumberGenerator; | ||
|
||
public class Car { | ||
|
||
private String name; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 불변인 변수는 final로 명시해주면 좋아요! (다른 클래스에도 함께 반영 부탁드려요) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. final을 명시하면 재할당 할 수 없기 때문에 안전하고, 불변이라는 것을 명확하게 전달하기 때문에 가독성 면에서도 좋을 것 같습니다 ! |
||
private int position; | ||
private final NumberGenerator generator; | ||
private static final int MOVE_THRESHOLD = 4; | ||
|
||
public Car(String name, NumberGenerator generator) { | ||
validateName(name); | ||
this.name = name.trim(); | ||
this.position = 0; | ||
this.generator = generator; | ||
} | ||
boya-go marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public int getPosition() { | ||
return position; | ||
} | ||
|
||
private void validateName(String name) { | ||
|
||
if (name == null || name.isBlank()) { | ||
throw new IllegalArgumentException("이름은 빈 값이 될 수 없습니다."); | ||
} | ||
if (name.length() > 5) { | ||
throw new IllegalArgumentException("이름은 5자 이하만 가능합니다."); | ||
} | ||
} | ||
|
||
public void movePosition() { | ||
if (generator.generate() >= MOVE_THRESHOLD ) { | ||
position++; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package domain; | ||
|
||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
public class CarRaceGame { | ||
|
||
private List<Car> cars; | ||
private List<String> winners; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. winners를 멤버 변수로 같게한 이유가 궁금해요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. getWinnerNames() 메서드에서 바로 사용할 수 있게끔 하려고 했었습니다. 메서드 내부의 지역변수로 수정했습니다 ! |
||
|
||
public CarRaceGame(List<Car> cars) { | ||
this.cars = cars; | ||
notAllowDuplicatedCarNames(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 변수에 값을 할당하고 검증하는것과 검증 후 할당하는 것은 어떠한 차이가 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 변수에 값 할당 후 검증하면 만약 유효하지 않았을 시에도 변수에 그 값이 저장되기 때문에 안정적이지 못합니다. 따라서 |
||
|
||
private void notAllowDuplicatedCarNames() { | ||
List<String> carNames = cars.stream() | ||
.map(Car::getName) | ||
.collect(Collectors.toList()); | ||
|
||
Set<String> carNameSet = new HashSet<>(carNames); // 중복된 이름은 자동으로 제거됨 | ||
|
||
// 만약 리스트의 크기와 Set의 크기가 다르면 중복이 있다는 의미 | ||
if (carNames.size() != carNameSet.size()) { | ||
throw new IllegalArgumentException("중복된 자동차 이름은 입력할 수 없습니다."); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 왜 따로 주석을 달아주셨나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 삭제하는 것을 깜빡했습니다 ! 😅 |
||
} | ||
|
||
public void validateRoundNumber(int gameRound) { | ||
if (gameRound < 1 ) { | ||
throw new IllegalArgumentException("라운드는 한 번 이상 진행되어야 합니다."); | ||
} | ||
} | ||
|
||
public void playOneRound() { | ||
for (Car car : cars) { | ||
car.movePosition(); | ||
} | ||
} | ||
|
||
private int getMaxDistance() { | ||
int maxDistance = cars.stream().mapToInt(Car::getPosition).max().orElseThrow(); | ||
|
||
return maxDistance; | ||
} | ||
|
||
private List<String> getWinners(int maxDistance) { | ||
List<String> winners = cars.stream().filter(car -> car.getPosition() == maxDistance).map(Car::getName).toList(); | ||
|
||
return winners; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. method chaining 시 개행을 이용하면 가독성을 높일 수 있어요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 개행하여 가독성을 높이고, 변수로 할당할 필요 없는 경우 바로 return하게끔 수정하였습니다 ! |
||
|
||
public void playRacingGame(int gameRound) { | ||
for (int i=0; i < gameRound;i++) { | ||
playOneRound(); | ||
} | ||
} | ||
|
||
public List<String> getWinnerNames() { | ||
winners = getWinners(getMaxDistance()); | ||
|
||
if (winners == null) { | ||
throw new IllegalStateException("게임이 실행되지 않았습니다."); | ||
} | ||
return winners; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package domain.Generator; | ||
|
||
public class FixedNumberGenerator implements NumberGenerator { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 테스트에서만 사용하기 위한 구현체라면 test 패키지에 두는게 좋아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 테스트에만 사용한다는 것을 명시적으로 나타낼 수 있어 혼돈을 방지할 수 있을 것 같습니다. |
||
private final int[] numbers; | ||
private int index = 0; | ||
|
||
public FixedNumberGenerator(int... numbers) { | ||
this.numbers = numbers; | ||
} | ||
|
||
@Override | ||
public int generate() { | ||
return numbers[index++]; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package domain.Generator; | ||
|
||
public interface NumberGenerator { | ||
int generate(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 랜덤한 로직을 통제하기 위해 인터페이스를 통해 추상화한 부분 좋습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 함수형 인터페이스로 만들면 따로 클래스를 만들 필요 없이 람다식으로 간결하게 활용할 수 있군요! 🤔 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package domain.Generator; | ||
|
||
import java.util.Random; | ||
|
||
public class RandomNumberGenerator implements NumberGenerator { | ||
|
||
private static final Random RANDOM = new Random(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Random 객체를 상수로 잘 만들어주셌네요👍 |
||
|
||
@Override | ||
public int generate( ) { | ||
return RANDOM.nextInt( 10 ); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package view; | ||
|
||
import java.util.Scanner; | ||
|
||
public class inputView { | ||
|
||
private static final Scanner scanner = new Scanner(System.in); | ||
|
||
public static String enterCarNames() { | ||
System.out.println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)."); | ||
return scanner.nextLine(); | ||
} | ||
|
||
public static int enterRoundNumber() { | ||
System.out.println("시도할 회수는 몇회인가요?"); | ||
return scanner.nextInt(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package view; | ||
|
||
import domain.Car; | ||
|
||
import java.util.List; | ||
|
||
public class outputView { | ||
|
||
private static final String CAR_POSITION_EXPRESSION = "-"; | ||
private static final String WINNER_NAMES_DELIMITER = ","; | ||
|
||
public static void printGameResultTitle() { | ||
System.out.println("\n실행 결과"); | ||
} | ||
|
||
public static void printRoundResult(List<Car> cars) { | ||
for (Car car : cars) { | ||
System.out.println(car.getName() + ": " + CAR_POSITION_EXPRESSION.repeat(car.getPosition())); | ||
} | ||
System.out.println(); | ||
} | ||
|
||
public static void printGameWinners(List<String> winners) { | ||
System.out.println(String.join(WINNER_NAMES_DELIMITER, winners) + "가 최종 우승했습니다."); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DELIMITER
가 어떤 목적의 구분자인지 표현할 수 있게 상수명을 변경해보면 어떨까요?추가적으로 input string에 대한 parsing 역할을 contoller에게 부여한 이유가 궁금해요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
문자열에서 구분자
,
를 기준으로 차 이름을 나누므로CAR_NAME_DELIMITER
로 수정했습니다.CarRaceGame 생성자에 List가 파라미터인데 리스트를 만들고 넣어줘야 한다고 생각해서 controller에 로직이 들어갔습니다. parsing 역할을 따로 클래스로 분리하여 도메인 패키지에 포함시키고 controller에서 그 클래스의 메서드를 이용하는 것으로 수정했습니다 !