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

[자동차 경주] 최유정 미션 제출합니다 #2373

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
21 changes: 21 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
작성한 코드를 통해 여러 가지 중요한 프로그래밍 원칙과 디자인 패턴에 대해 배우고 고민할 수 있었습니다.
### 1. **책임의 분리(Separation of Concerns)**:
- **Domain Logic의 분리**: `RacingCar`, `RacingCarManager`, `GameManager`와 같은 클래스들은 레이싱 게임의 도메인 로직을 담당합니다. 이들은 게임의 상태를 관리하고 게임 규칙을 실행하는 책임을 갖습니다.
- **Input/Output 처리의 분리**: `UserInputUtil`과 `GamePrinter` 클래스는 사용자 입력과 게임 결과의 출력을 담당합니다. 이렇게 함으로써 도메인 로직과 UI 로직을 분리하여 각각의 변경에 덜 영향을 받게 만들었습니다.

### 2. **재사용성과 유지보수성 향상**:
- **Utility 클래스의 사용**: `UserInputUtil`과 같은 유틸리티 클래스를 사용함으로써, 사용자 입력 처리 로직을 한 곳에 모아 관리할 수 있게 되었습니다. 이는 코드의 중복을 줄이고 재사용성을 향상시켰습니다.
- **상수의 중앙 관리**: `RacingCarConstant` 클래스를 통해 상수 값을 한 곳에서 관리하게 되어, 값을 변경할 때 한 곳만 수정하면 되므로 유지보수성이 향상되었습니다.

### 3. **객체 지향 프로그래밍(OOP) 원칙 적용**:
- **Encapsulation(캡슐화)**: 각 클래스는 자신의 상태를 적젹적으로 숨기고, 상태를 변경할 수 있는 메서드를 제공합니다. 예를 들어, `RacingCar` 클래스는 `position` 상태를 외부에 직접 노출하지 않고, `move()` 메서드를 통해 상태를 변경합니다.
- **Single Responsibility Principle (SRP, 단일 책임 원칙)**: 각 클래스는 하나의 책임만을 갖습니다. 예를 들어, `UserInputUtil` 클래스는 사용자 입력을 처리하는 책임만을 갖고, `RacingCarManager` 클래스는 게임 로직을 실행하는 책임만을 갖습니다.

### 4. **유연성과 확장성 고려**:
- **Strategy Pattern의 적용**: 랜덤 값을 생성하는 로직을 `RandomNumGenerator` 클래스로 분리하여, 필요할 경우 다른 랜덤 값 생성 전략으로 쉽게 교체할 수 있게 설계하였습니다.
- **상속/인터페이스 사용 고려**: 현재는 간단한 애플리케이션으로 인터페이스나 추상 클래스를 사용할 필요가 없지만, 게임의 규모가 커지고 다양한 타입의 레이싱 게임을 지원해야 한다면 인터페이스나 추상 클래스를 통해 확장성을 높일 수 있습니다.

### 5. **Stream API의 활용**:
- Java 8의 Stream API를 사용하여 컬렉션 처리 로직을 간결하고 선언적으로 작성할 수 있었습니다. 이는 코드의 가독성을 향상시켜주고, 버그의 가능성을 줄여줍니다.

이러한 원칙과 패턴들을 적용함으로써, 코드의 유지보수성, 확장성, 가독성을 향상시키고 버그 발생 가능성을 줄일 수 있었습니다.
5 changes: 4 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package racingcar;

import racingcar.domain.GameManager;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
GameManager gameManager = new GameManager();
gameManager.startGame();
}
}
42 changes: 42 additions & 0 deletions src/main/java/racingcar/domain/GameManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package racingcar.domain;

import org.assertj.core.error.uri.ShouldHaveUserInfo;
import racingcar.util.GamePrinter;
import racingcar.util.RacingCarConstant;
import racingcar.util.UserInputUtil;

import java.util.List;

/*
* 게임 실행
* */
public class GameManager {

private RacingCarManager racingCarManager;
private int tries;

public void startGame() throws IllegalArgumentException {
initializeGame();
playGame();
endGame();
}

private void initializeGame() {
List<String> carNameList = UserInputUtil.getCarNames();
this.tries = UserInputUtil.getTries();
this.racingCarManager = new RacingCarManager(carNameList);
}

private void playGame() {
System.out.println(RacingCarConstant.RESULT_HEADER_MESSAGE);
for (int i = 0; i < tries; i++) {
racingCarManager.playOneRound();
GamePrinter.printRoundResult(racingCarManager.getCars());
}
}

private void endGame() {
List<String> winners = racingCarManager.findWinners();
GamePrinter.printWinners(winners);
}
}
31 changes: 31 additions & 0 deletions src/main/java/racingcar/domain/RacingCar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package racingcar.domain;

import racingcar.util.RacingCarConstant;

public class RacingCar {

private final String name;
private int position = 0;

public RacingCar(String name) {
String trimmedName = name.trim();
if (trimmedName.length() > RacingCarConstant.CAR_NAME_MAX_LENGTH || trimmedName.isEmpty()) {
throw new IllegalArgumentException(RacingCarConstant.CAR_NAME_LENGTH_ERROR_MESSAGE);
}
this.name = trimmedName;
}
public void move() {
int randomValue = RandomNumGenerator.generate();
if (randomValue >= RacingCarConstant.MOVE_THRESHOLD) {
position++;
}
}

public String getName() {
return name;
}

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

import java.util.ArrayList;
import java.util.List;

public class RacingCarManager {

private List<RacingCar> cars;

public RacingCarManager(List<String> carNames) {
this.cars = new ArrayList<>();
for (String carName : carNames) {
cars.add(new RacingCar(carName));
}
}

public void playOneRound() {
for (RacingCar car : cars) {
car.move();
}
}

public List<RacingCar> getCars() {
return cars;
}

public List<String> findWinners() {
RacingCarRankingManager rankingManager = new RacingCarRankingManager();
return rankingManager.getWinners(cars);
}
}
18 changes: 18 additions & 0 deletions src/main/java/racingcar/domain/RacingCarRankingManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package racingcar.domain;

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

public class RacingCarRankingManager {
public List<String> getWinners(List<RacingCar> cars) {
int maxPosition = cars.stream()
.mapToInt(RacingCar::getPosition)
.max()
.orElse(0);

return cars.stream()
.filter(car -> car.getPosition() == maxPosition)
.map(RacingCar::getName)
.collect(Collectors.toList());
}
}
9 changes: 9 additions & 0 deletions src/main/java/racingcar/domain/RandomNumGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar.domain;

import camp.nextstep.edu.missionutils.Randoms;

public class RandomNumGenerator {
public static int generate() {
return Randoms.pickNumberInRange(0, 9);
}
}
18 changes: 18 additions & 0 deletions src/main/java/racingcar/util/GamePrinter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package racingcar.util;

import racingcar.domain.RacingCar;

import java.util.List;

public class GamePrinter {
public static void printRoundResult(List<RacingCar> cars) {
for (RacingCar car : cars) {
System.out.println(car.getName() + " : " + "-".repeat(car.getPosition()));
}
System.out.println();
}

public static void printWinners(List<String> winners) {
System.out.println("최종 우승자 : " + String.join(", ", winners));
}
}
13 changes: 13 additions & 0 deletions src/main/java/racingcar/util/RacingCarConstant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package racingcar.util;

public class RacingCarConstant {

public static final String INPUT_CAR_NAMES_MESSAGE = "경주할 자동차 이름을 입력하세요. (이름은 쉼표(,) 기준으로 구분)";
public static final String INPUT_TRIES_MESSAGE = "시도할 횟수는 몇 회인가요?";
public static final String RESULT_HEADER_MESSAGE = "실행 결과";
public static final String NUMBER_INPUT_ERROR_MESSAGE = "숫자를 입력해주세요.";
public static final String INVALID_CAR_NAMES_INPUT = "자동차 이름 입력이 올바르지 않습니다. 쉼표로 구분해주세요.";
public static final String CAR_NAME_LENGTH_ERROR_MESSAGE = "자동차 이름은 5자 이하로 입력해주세요.";
public static final int CAR_NAME_MAX_LENGTH = 5;
public static final int MOVE_THRESHOLD = 4;
}
43 changes: 43 additions & 0 deletions src/main/java/racingcar/util/UserInputUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package racingcar.util;

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

import camp.nextstep.edu.missionutils.Console;


public class UserInputUtil {

public static List<String> getCarNames() {
System.out.println(RacingCarConstant.INPUT_CAR_NAMES_MESSAGE);
String input = Console.readLine();
validateCarNamesInput(input);
List<String> carNames = Arrays.stream(input.split(","))
.map(String::trim)
.collect(Collectors.toList());
return carNames;
}

private static void validateCarNamesInput(String input) {
if (input.startsWith(",") || input.endsWith(",") || input.contains(",,") || input.trim().isEmpty()) {
throw new IllegalArgumentException(RacingCarConstant.INVALID_CAR_NAMES_INPUT);
}
String[] names = input.split(",");
for (String name : names) {
if (name.trim().isEmpty()) {
throw new IllegalArgumentException(RacingCarConstant.INVALID_CAR_NAMES_INPUT);
}
}
}

public static int getTries() {
System.out.println(RacingCarConstant.INPUT_TRIES_MESSAGE);
try {
return Integer.parseInt(Console.readLine());
} catch (NumberFormatException e) {
System.out.println(RacingCarConstant.NUMBER_INPUT_ERROR_MESSAGE);
return getTries();
}
}
}