-
Notifications
You must be signed in to change notification settings - Fork 450
[1단계 - 자동차 경주 구현] 미아(이종미) 미션 제출합니다. #663
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
Changes from all commits
eb38c39
9b41a8d
3bb9da8
96ed9f7
bc87041
5641bfa
be081af
5cd7dcf
8770488
4f55e91
02fd68a
66c07da
c25f173
fa8cf97
c12059b
9b1eea8
93d4827
5038390
60f3877
c6abad0
044b15a
acedb4e
bb6efbe
12b2de5
b0085b2
e368b06
c9b5df6
3afe80f
4627def
8ae2b44
b41c3b4
0abac6e
94a373a
e889b38
0803b83
fd77b3d
e49082a
13d1393
2f6c7ef
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 |
|---|---|---|
| @@ -1,7 +1,36 @@ | ||
| # java-racingcar | ||
| # [자동차 경주] 구현 기능 목록 | ||
|
|
||
| 자동차 경주 미션 저장소 | ||
| ## 핵심 구현 기능 | ||
|
|
||
| ## 우아한테크코스 코드리뷰 | ||
| 1. 이름을 가진 N대의 자동차를 생성한다. | ||
| 2. 사용자가 입력한 횟수만큼 각 자동차는 전진 또는 정지할 수 있다. | ||
| 3. 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다. | ||
| 4. 한 명 이상의 우승자를 구한다. | ||
| 1. 모든 자동차의 전진 횟수가 0회인 경우도 우승으로 간주한다. | ||
|
|
||
| - [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) | ||
| ## 입출력 기능 | ||
|
|
||
| ### 입력 | ||
|
|
||
| 1. 자동차 이름들을 담은 문자열을 입력한다. | ||
| 1. 각 자동차 이름은 다섯 글자 이하로 한다. | ||
| 2. 각 자동차 이름은 쉼표로 구분된다. | ||
| 2. 시도할 회수를 입력한다. | ||
|
|
||
| ### 출력 | ||
|
|
||
| 1. 각 시도 마다 실행 결과를 출력한다. | ||
| 1. 이름의 순서는 입력된 순서로 출력한다. | ||
| 2. 1회 전진 시 ‘-’ 1개를 출력한다. | ||
| 3. 1회 정지 시 아무것도 출력하지 않는다. | ||
| 2. 우승자 이름을 출력한다. | ||
| 1. 이름의 순서는 입력된 순서로 출력한다. | ||
| 2. 각 이름은 쉼표로 구분한다. | ||
|
|
||
| ## 예외 처리 기능 | ||
|
|
||
| 1. 자동차 이름이 1자 미만 5자 초과인 경우 예외로 처리한다. | ||
| 2. 중복된 자동차 이름이 입력된 경우 예외로 처리한다. | ||
| 3. 아예 빈 문자열 혹은 null이 입력된 경우 예외로 처리한다. | ||
| 4. 시도할 회수에 정수가 아닌 문자열이 입력된 경우 예외로 처리한다. | ||
| 5. 시도할 회수에 0 또는 양이 아닌 정수가 입력된 경우 예외로 처리한다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package racingcar; | ||
|
|
||
| import racingcar.manager.RacingManager; | ||
|
|
||
| public class Application { | ||
| public static void main(String[] args) { | ||
| RacingManager manager = new RacingManager(); | ||
| manager.racingController().start(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| package racingcar.controller; | ||
|
|
||
| import racingcar.domain.Car; | ||
| import racingcar.domain.Cars; | ||
| import racingcar.domain.RandomNumberGenerator; | ||
| import racingcar.domain.RandomNumberGeneratorImpl; | ||
| import racingcar.util.Parser; | ||
| import racingcar.util.Validator; | ||
| import racingcar.view.InputView; | ||
| import racingcar.view.OutputView; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class RacingController { | ||
| private final InputView inputView; | ||
| private final OutputView outputView; | ||
|
|
||
| public RacingController(final InputView inputView, final OutputView outputView) { | ||
| this.inputView = inputView; | ||
| this.outputView = outputView; | ||
| } | ||
|
|
||
| public void start() { | ||
| final List<String> carNames = readCarNames(); | ||
| final Cars cars = new Cars(carNames); | ||
| final int tryCount = readTryCount(); | ||
|
|
||
| outputView.printResultMsg(); | ||
| RandomNumberGenerator randomNumberGenerator = new RandomNumberGeneratorImpl(); | ||
| for(int i = 0 ; i < tryCount; i++) { | ||
| moveCars(cars, randomNumberGenerator); | ||
| } | ||
|
|
||
| final List<Car> winners = cars.determineWinner(); | ||
| outputView.printWinners(winners); | ||
| inputView.closeScanner(); | ||
| } | ||
|
|
||
| private List<String> readCarNames() { | ||
| try { | ||
| String carNames = inputView.readCarNames(); | ||
| Validator.validateNullName(carNames); | ||
| List<String> parsedCarNames = Parser.parseCarNames(carNames); | ||
| Validator.validateCarNames(parsedCarNames); | ||
|
Comment on lines
+42
to
+44
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. 이름에 대한 고민들을 해볼까요? Validator나 Parser의 클래스명을 보았을 때, 해당 클래스는 어떤 일을 하는 것으로 유추할 수 있을까요? 이 부분에 대해서 고민하다보면
Author
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. 유효성 검증(Validator)과 분석(Parser)하는 클래스로 유추될 거 같아요. 정확하고 구체적으로 하는 일을 유추하기 어렵네요.. 검증하고 분석하는 대상을 쉽게 찾을 수 있으려면, view나 domain으로 메서드 위치를 옮기는게 좋을 거 같네요! 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. 예를 들어 CarNames의 검증 역할이 Controller에 있다면, Cars라는 도메인은 자신의 이름을 검증할 책임이 없게 됩니다. 반대로 도메인에 있다면, Cars라는 도메인은 자신의 이름을 검증할 책임이 있게 됩니다. 이렇게 어디다가 두는 것이 옳다!가 아니라 미아의 의도에 맞춰서 도메인들을 설계해주는 것이 중요합니다. (이 부분은 코드를 짜면서 익히는 것도 좋지만, 1레벨 필독서들을 읽으면 또 다른 느낌으로 와닿을 것이라 생각해요!! |
||
| return parsedCarNames; | ||
| } catch (IllegalArgumentException e) { | ||
| outputView.printErrorMsg(e.getMessage()); | ||
| return readCarNames(); | ||
| } | ||
| } | ||
|
|
||
| private int readTryCount() { | ||
| try { | ||
| String tryCount = inputView.readTryCount(); | ||
| int parsedTryCount = Validator.validateInteger(tryCount); | ||
| Validator.validateTryCount(parsedTryCount); | ||
| return parsedTryCount; | ||
| } catch (IllegalArgumentException e) { | ||
| outputView.printErrorMsg(e.getMessage()); | ||
| return readTryCount(); | ||
| } | ||
| } | ||
|
|
||
| private void moveCars(final Cars cars, final RandomNumberGenerator randomNumberGenerator) { | ||
| cars.moveAll(randomNumberGenerator); | ||
| outputView.printCarPosition(cars); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package racingcar.domain; | ||
|
|
||
| public class Car { | ||
| private static final int MOVEMENT_CRITERIA = 3; | ||
|
|
||
| private final String name; | ||
| private int movement; | ||
|
|
||
| public Car(final String name) { | ||
| this.name = name; | ||
| this.movement = 0; | ||
| } | ||
|
|
||
| public void move(final int condition) { | ||
| if (condition > MOVEMENT_CRITERIA ) { | ||
| this.movement += 1; | ||
| } | ||
| } | ||
|
|
||
| public boolean isSameCount(final int count) { | ||
|
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. movement로 바뀌었지만 요건 count로 남아있네요 😃 |
||
| return this.movement == count; | ||
| } | ||
|
|
||
| public boolean isAlsoWinner(final Car car) { | ||
| return car.isSameCount(movement); | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| public int getMovement() { | ||
| return movement; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package racingcar.domain; | ||
|
|
||
| import java.util.Comparator; | ||
| import java.util.List; | ||
|
|
||
| public class Cars { | ||
| private final List<Car> cars; | ||
|
|
||
| public Cars(final List<String> names) { | ||
| this.cars = names.stream().map(Car::new).toList(); | ||
| } | ||
|
|
||
| public void moveAll(final RandomNumberGenerator randomNumberGenerator) { | ||
| for(Car car: cars) { | ||
| final int condition = randomNumberGenerator.generate(); | ||
| car.move(condition); | ||
| } | ||
| } | ||
|
|
||
| public List<Car> getCars() { | ||
| return this.cars; | ||
| } | ||
|
|
||
| public List<Car> determineWinner() { | ||
| final Car winnerCar = cars.stream() | ||
| .max(Comparator.comparing(Car::getMovement)) | ||
| .get(); | ||
| return cars.stream() | ||
| .filter(car -> car.isAlsoWinner(winnerCar)) | ||
| .toList(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package racingcar.domain; | ||
|
|
||
| public interface RandomNumberGenerator { | ||
| int generate(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package racingcar.domain; | ||
|
|
||
| import java.util.Random; | ||
|
|
||
| public class RandomNumberGeneratorImpl implements RandomNumberGenerator { | ||
| public int generate() { | ||
| Random random = new Random(); | ||
| return random.nextInt(10); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package racingcar.manager; | ||
|
|
||
| import racingcar.controller.RacingController; | ||
| import racingcar.view.InputView; | ||
| import racingcar.view.OutputView; | ||
|
|
||
| public class RacingManager { | ||
| private InputView inputView() { | ||
|
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 new InputView(); | ||
| } | ||
|
|
||
| private OutputView outputView() { | ||
| return new OutputView(); | ||
| } | ||
|
|
||
| public RacingController racingController() { | ||
| return new RacingController(inputView(), outputView()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package racingcar.util; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
|
|
||
| public class Parser { | ||
| public static List<String> parseCarNames(final String input) { | ||
| return Arrays.stream(input.split(",")) | ||
| .map(String::trim) | ||
| .toList(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package racingcar.util; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| public class Validator { | ||
| public static void validateNullName(final String carNames) { | ||
| if(carNames == null) { | ||
| throw new IllegalArgumentException("자동차 이름 목록은 null일 수 없습니다."); | ||
| } | ||
| } | ||
|
|
||
| public static void validateCarNames(final List<String> carNames) { | ||
| for(String carName: carNames) { | ||
| validateCarNamesFormat(carName); | ||
| } | ||
| validateDuplicatedNames(carNames); | ||
| } | ||
|
|
||
| private static void validateCarNamesFormat(final String carName) { | ||
| if (carName.isBlank() || carName.isEmpty() || carName.length() > 5) { | ||
| throw new IllegalArgumentException("자동차 이름은 1자 이상 5자 이하여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| private static void validateDuplicatedNames(final List<String> carNames) { | ||
| final Set<String> uniqueNames = Set.copyOf(carNames); | ||
| if(uniqueNames.size() != carNames.size()) { | ||
| throw new IllegalArgumentException("자동차 이름은 중복될 수 없습니다."); | ||
| } | ||
| } | ||
|
|
||
| public static int validateInteger(final String tryCount) { | ||
| try { | ||
| return Integer.parseInt(tryCount); | ||
| } catch (NumberFormatException e) { | ||
| throw new IllegalArgumentException("시도 회수는 정수여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| public static void validateTryCount(final int tryCount) { | ||
| if (tryCount < 1) { | ||
| throw new IllegalArgumentException("시도할 회수는 양의 정수여야 합니다."); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package racingcar.view; | ||
|
|
||
| import java.util.Scanner; | ||
|
|
||
| public class InputView { | ||
| private static final String CAR_NAMES_INPUT_MSG = "경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)."; | ||
| private static final String TRY_COUNT_INPUT_MSG = "시도할 회수는 몇회인가요?"; | ||
|
Comment on lines
+6
to
+7
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. 코드에 어떤 부분은 상수로 문자열을 표시하고, 어떤 부분은 직접 문자열을 표시하고 있네요.
Author
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. 역할 설명이 필요하다고 생각하는 문자열은 static final 으로 선언하고자 했는데, 추가로 필요한 부분이 있는지 다시 살펴보겠습니다! |
||
|
|
||
| private final Scanner scanner = new Scanner(System.in); | ||
|
|
||
| public String readCarNames() { | ||
| System.out.println(CAR_NAMES_INPUT_MSG); | ||
| return scanner.nextLine(); | ||
| } | ||
|
|
||
| public String readTryCount() { | ||
| System.out.println(TRY_COUNT_INPUT_MSG); | ||
| return scanner.nextLine(); | ||
| } | ||
|
|
||
| public void closeScanner() { | ||
| scanner.close(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package racingcar.view; | ||
|
|
||
| import java.util.List; | ||
| import racingcar.domain.Car; | ||
| import racingcar.domain.Cars; | ||
|
|
||
| public class OutputView { | ||
| private static final String RESULT_MSG = "실행 결과"; | ||
| private static final String POSITION_FORM = "%s : %s"; | ||
| private static final String TRACE = "-"; | ||
| private static final String WINNER_MSG = "%s가 최종 우승했습니다."; | ||
|
|
||
| public void printCarPosition(final Cars cars) { | ||
| for(Car car : cars.getCars()) { | ||
| final String name = car.getName(); | ||
| final String traces = makeTraces(car.getMovement()); | ||
| System.out.println(String.format(POSITION_FORM, name, traces)); | ||
| } | ||
| System.out.println(); | ||
| } | ||
|
|
||
| private String makeTraces(final int count) { | ||
| return TRACE.repeat(count); | ||
| } | ||
|
|
||
| public void printWinners(final List<Car> winners) { | ||
| List<String> winnerNames = winners.stream().map(Car::getName).toList(); | ||
| final String names = String.join(", ", winnerNames); | ||
| System.out.println(String.format(WINNER_MSG, names)); | ||
| } | ||
|
|
||
| public void printResultMsg() { | ||
| System.out.println(RESULT_MSG); | ||
| } | ||
|
|
||
| public void printErrorMsg(String errorMsg) { | ||
| System.out.println(errorMsg); | ||
| } | ||
| } |
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.
각종 곳에
final이 붙어있는데, final을 붙였을 때 어떤 장/단점이 있을지그에 따라 어디에
final을 붙이는 것이 가장 좋을지 고민해보는 것도 좋습니다.이 부분은 취향이 갈리는 부분이라 크루들과 이야기를 나눠보는 것도 좋을 것 같아요.
미아만의 기준을 찾아보길 바랍니다.