diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..2561497f5aa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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를 사용하여 컬렉션 처리 로직을 간결하고 선언적으로 작성할 수 있었습니다. 이는 코드의 가독성을 향상시켜주고, 버그의 가능성을 줄여줍니다. + +이러한 원칙과 패턴들을 적용함으로써, 코드의 유지보수성, 확장성, 가독성을 향상시키고 버그 발생 가능성을 줄일 수 있었습니다. \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e7242..35beb5dee25 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -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(); } } diff --git a/src/main/java/racingcar/domain/GameManager.java b/src/main/java/racingcar/domain/GameManager.java new file mode 100644 index 00000000000..fb5eaf568d9 --- /dev/null +++ b/src/main/java/racingcar/domain/GameManager.java @@ -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 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 winners = racingCarManager.findWinners(); + GamePrinter.printWinners(winners); + } +} diff --git a/src/main/java/racingcar/domain/RacingCar.java b/src/main/java/racingcar/domain/RacingCar.java new file mode 100644 index 00000000000..a39194c240b --- /dev/null +++ b/src/main/java/racingcar/domain/RacingCar.java @@ -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; + } +} diff --git a/src/main/java/racingcar/domain/RacingCarManager.java b/src/main/java/racingcar/domain/RacingCarManager.java new file mode 100644 index 00000000000..d41056a1e0d --- /dev/null +++ b/src/main/java/racingcar/domain/RacingCarManager.java @@ -0,0 +1,31 @@ +package racingcar.domain; + +import java.util.ArrayList; +import java.util.List; + +public class RacingCarManager { + + private List cars; + + public RacingCarManager(List 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 getCars() { + return cars; + } + + public List findWinners() { + RacingCarRankingManager rankingManager = new RacingCarRankingManager(); + return rankingManager.getWinners(cars); + } +} diff --git a/src/main/java/racingcar/domain/RacingCarRankingManager.java b/src/main/java/racingcar/domain/RacingCarRankingManager.java new file mode 100644 index 00000000000..6c872826cf7 --- /dev/null +++ b/src/main/java/racingcar/domain/RacingCarRankingManager.java @@ -0,0 +1,18 @@ +package racingcar.domain; + +import java.util.List; +import java.util.stream.Collectors; + +public class RacingCarRankingManager { + public List getWinners(List 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()); + } +} diff --git a/src/main/java/racingcar/domain/RandomNumGenerator.java b/src/main/java/racingcar/domain/RandomNumGenerator.java new file mode 100644 index 00000000000..923e32b416f --- /dev/null +++ b/src/main/java/racingcar/domain/RandomNumGenerator.java @@ -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); + } +} diff --git a/src/main/java/racingcar/util/GamePrinter.java b/src/main/java/racingcar/util/GamePrinter.java new file mode 100644 index 00000000000..ffeaa188aea --- /dev/null +++ b/src/main/java/racingcar/util/GamePrinter.java @@ -0,0 +1,18 @@ +package racingcar.util; + +import racingcar.domain.RacingCar; + +import java.util.List; + +public class GamePrinter { + public static void printRoundResult(List cars) { + for (RacingCar car : cars) { + System.out.println(car.getName() + " : " + "-".repeat(car.getPosition())); + } + System.out.println(); + } + + public static void printWinners(List winners) { + System.out.println("최종 우승자 : " + String.join(", ", winners)); + } +} diff --git a/src/main/java/racingcar/util/RacingCarConstant.java b/src/main/java/racingcar/util/RacingCarConstant.java new file mode 100644 index 00000000000..6a7e92b5e15 --- /dev/null +++ b/src/main/java/racingcar/util/RacingCarConstant.java @@ -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; +} diff --git a/src/main/java/racingcar/util/UserInputUtil.java b/src/main/java/racingcar/util/UserInputUtil.java new file mode 100644 index 00000000000..a95b017afb8 --- /dev/null +++ b/src/main/java/racingcar/util/UserInputUtil.java @@ -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 getCarNames() { + System.out.println(RacingCarConstant.INPUT_CAR_NAMES_MESSAGE); + String input = Console.readLine(); + validateCarNamesInput(input); + List 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(); + } + } +}