From 969c7c22f6c7e61646fd12a516a7ddb30779d5aa Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 12:53:39 +0900 Subject: [PATCH 01/13] =?UTF-8?q?setting:=20=EA=B7=B8=EB=9D=BC=EB=93=A4=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=2017=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a89989b..15f3352 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ dependencies { java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(17) } } From 4683a624530b4fe19e5a4cb58ebea1b7dd25181e Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 12:54:31 +0900 Subject: [PATCH 02/13] =?UTF-8?q?feature:=20car=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/domain/car/Car.java | 36 +++++++++++++++++++ .../java/racingcar/domain/car/CarName.java | 23 ++++++++++++ .../domain/strategy/MovementStrategy.java | 5 +++ .../domain/strategy/RandomMovement.java | 14 ++++++++ 4 files changed, 78 insertions(+) create mode 100644 src/main/java/racingcar/domain/car/Car.java create mode 100644 src/main/java/racingcar/domain/car/CarName.java create mode 100644 src/main/java/racingcar/domain/strategy/MovementStrategy.java create mode 100644 src/main/java/racingcar/domain/strategy/RandomMovement.java diff --git a/src/main/java/racingcar/domain/car/Car.java b/src/main/java/racingcar/domain/car/Car.java new file mode 100644 index 0000000..d7c8782 --- /dev/null +++ b/src/main/java/racingcar/domain/car/Car.java @@ -0,0 +1,36 @@ +package racingcar.domain.car; + +import racingcar.domain.strategy.MovementStrategy; + +public class Car { + private final CarName name; + private int position; + + public Car(CarName name, int position) { + this.name = name; + this.position = position; + } + + public void advance(MovementStrategy strategy) { + if (strategy.shouldMove()) { + position++; + } + } + + public boolean isAtPosition(int position) { + return this.position == position; + } + + public int getPosition() { + return position; + } + + public String getName() { + return name.toString(); + } + + @Override + public String toString() { + return name.toString() + " : " + "-".repeat(position); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/domain/car/CarName.java b/src/main/java/racingcar/domain/car/CarName.java new file mode 100644 index 0000000..983003e --- /dev/null +++ b/src/main/java/racingcar/domain/car/CarName.java @@ -0,0 +1,23 @@ +package racingcar.domain.car; + +import static racingcar.constants.ErrorMessage.NAME_ERROR; + +public class CarName { + private final String value; + private static final int NAME_LIMIT_LENGTH = 5; + public CarName(String value) { + validateName(value); + this.value = value; + } + + private void validateName(String name) { + if (name.length() > NAME_LIMIT_LENGTH) { + throw new IllegalArgumentException(NAME_ERROR.toString()); + } + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/racingcar/domain/strategy/MovementStrategy.java b/src/main/java/racingcar/domain/strategy/MovementStrategy.java new file mode 100644 index 0000000..7a77a9b --- /dev/null +++ b/src/main/java/racingcar/domain/strategy/MovementStrategy.java @@ -0,0 +1,5 @@ +package racingcar.domain.strategy; + +public interface MovementStrategy { + boolean shouldMove(); +} diff --git a/src/main/java/racingcar/domain/strategy/RandomMovement.java b/src/main/java/racingcar/domain/strategy/RandomMovement.java new file mode 100644 index 0000000..0773df7 --- /dev/null +++ b/src/main/java/racingcar/domain/strategy/RandomMovement.java @@ -0,0 +1,14 @@ +package racingcar.domain.strategy; + +import camp.nextstep.edu.missionutils.Randoms; + +public class RandomMovement implements MovementStrategy { + private static final int MIN_VALUE = 0; + private static final int MAX_VALUE = 9; + private static final int THRESHOLD = 4; + + @Override + public boolean shouldMove() { + return Randoms.pickNumberInRange(MIN_VALUE, MAX_VALUE) >= THRESHOLD; + } +} \ No newline at end of file From f69f25581f0533244fce0adcfd7df0939d8a7f07 Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 12:54:56 +0900 Subject: [PATCH 03/13] =?UTF-8?q?feature:=20view=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/view/ConsoleView.java | 65 +++++++++++++++++++ src/main/java/racingcar/view/View.java | 11 ++++ 2 files changed, 76 insertions(+) create mode 100644 src/main/java/racingcar/view/ConsoleView.java create mode 100644 src/main/java/racingcar/view/View.java diff --git a/src/main/java/racingcar/view/ConsoleView.java b/src/main/java/racingcar/view/ConsoleView.java new file mode 100644 index 0000000..84252cc --- /dev/null +++ b/src/main/java/racingcar/view/ConsoleView.java @@ -0,0 +1,65 @@ +package racingcar.view; + +import camp.nextstep.edu.missionutils.Console; +import java.util.List; + +import static racingcar.constants.ErrorMessage.*; + +public class ConsoleView implements View { + private static final String CAR_NAMES_DELIMITER = ","; + private static final String INPUT_NAME = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"; + private static final String INPUT_CHANCE = "시도할 회수는 몇회인가요?"; + private static final String OUTPUT_MESSAGE="\n실행 결과"; + private static final String OUTPUT_FINAL_WINNER="최종 우승자 : "; + + @Override + public String[] readCarNames() { + System.out.println(INPUT_NAME); + String input = Console.readLine(); + validateCarNames(input); + return input.split(CAR_NAMES_DELIMITER); + } + + @Override + public int readAttempts() { + System.out.println(INPUT_CHANCE); + String input = Console.readLine(); + return validateAndParseAttempts(input); + } + + @Override + public void printRaceStart() { + System.out.println(OUTPUT_MESSAGE); + } + + @Override + public void printRoundResult(String roundResult) { + System.out.println(roundResult); + System.out.println(); + } + + @Override + public void printWinners(List winners) { + System.out.print(OUTPUT_FINAL_WINNER); + System.out.println(String.join(", ", winners)); + } + + private void validateCarNames(String input) { + if (input == null || input.trim().isEmpty()) { + throw new IllegalArgumentException(NAME_ERROR.toString()); + } + //중복 등 메서드 추가 예정 + } + + private int validateAndParseAttempts(String input) { + try { + int attempts = Integer.parseInt(input); + if (attempts <= 0) { + throw new IllegalArgumentException(CHANCE_NEGATIVE_ERROR.toString()); + } + return attempts; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(CHANCE_TYPE_ERROR.toString()); + } + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/view/View.java b/src/main/java/racingcar/view/View.java new file mode 100644 index 0000000..dfa1806 --- /dev/null +++ b/src/main/java/racingcar/view/View.java @@ -0,0 +1,11 @@ +package racingcar.view; + +import java.util.List; + +public interface View { + String[] readCarNames(); + int readAttempts(); + void printRaceStart(); + void printRoundResult(String roundResult); + void printWinners(List winners); +} \ No newline at end of file From 0614bc476017754fd8dbdc491e45b2823d8389cc Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 12:55:25 +0900 Subject: [PATCH 04/13] =?UTF-8?q?feature:=20ErrorMessage=20enum=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/racingcar/constants/ErrorMessage.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/racingcar/constants/ErrorMessage.java diff --git a/src/main/java/racingcar/constants/ErrorMessage.java b/src/main/java/racingcar/constants/ErrorMessage.java new file mode 100644 index 0000000..f198110 --- /dev/null +++ b/src/main/java/racingcar/constants/ErrorMessage.java @@ -0,0 +1,18 @@ +package racingcar.constants; + +public enum ErrorMessage { + NAME_ERROR("자동차 이름을 입력해야 합니다."), + NAME_TPYE_ERROR("자동차 이름은 5자 이하만 가능합니다."), + + CHANCE_NEGATIVE_ERROR("시도 횟수는 양수여야 합니다."), + CHANCE_TYPE_ERROR("시도 횟수는 숫자여야 합니다."); + + private String message; + ErrorMessage(String message) { + this.message = message; + } + @Override + public String toString() { + return message; + } +} From aff080f5cba87642a9112402d33ec17b83995ee4 Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 12:55:58 +0900 Subject: [PATCH 05/13] =?UTF-8?q?feature:=20=EB=A1=9C=EC=A7=81=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EB=8A=94=20manager=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/racingcar/domain/RaceManager.java | 48 ++++++++++++++++++ src/main/java/racingcar/domain/car/Cars.java | 49 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/main/java/racingcar/domain/RaceManager.java create mode 100644 src/main/java/racingcar/domain/car/Cars.java diff --git a/src/main/java/racingcar/domain/RaceManager.java b/src/main/java/racingcar/domain/RaceManager.java new file mode 100644 index 0000000..0a8037c --- /dev/null +++ b/src/main/java/racingcar/domain/RaceManager.java @@ -0,0 +1,48 @@ +package racingcar.domain; + +import racingcar.dto.RoundResultDto; +import racingcar.domain.car.Car; +import racingcar.domain.car.Cars; +import racingcar.domain.strategy.MovementStrategy; +import racingcar.domain.strategy.RandomMovement; +import racingcar.view.View; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class RaceManager { + private final View view; + private final MovementStrategy movementStrategy; + private Cars cars; + + public RaceManager(View view) { + this.view = view; + this.movementStrategy = new RandomMovement(); + } + + public void start() { + String[] carNames = view.readCarNames(); + this.cars = new Cars(Arrays.asList(carNames)); + + int attempts = view.readAttempts(); + view.printRaceStart(); + + for (int i = 0; i < attempts; i++) { + executeRound(); + } + announceWinners(); + } + + private void executeRound() { + cars.moveAll(movementStrategy); + RoundResultDto result = new RoundResultDto( + cars.getRacers().stream() + .map(Car::toString) + .collect(Collectors.toList()) + ); + view.printRoundResult(result.formatResult()); + } + + private void announceWinners() { + view.printWinners(cars.findWinnerNames()); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/domain/car/Cars.java b/src/main/java/racingcar/domain/car/Cars.java new file mode 100644 index 0000000..7e8c9a6 --- /dev/null +++ b/src/main/java/racingcar/domain/car/Cars.java @@ -0,0 +1,49 @@ +package racingcar.domain.car; + +import racingcar.domain.strategy.MovementStrategy; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class Cars { + private final List racers; + private static final int INITIAL_POSITION = 0; + + public Cars(List names) { + this.racers = names.stream() + .map(name -> new Car(new CarName(name), INITIAL_POSITION)) + .collect(Collectors.toList()); + } + + public void moveAll(MovementStrategy strategy) { + racers.forEach(car -> car.advance(strategy)); + } + + public List findWinnerNames() { + int maxPosition = findMaxPosition(); + + return racers.stream() + .filter(car -> car.isAtPosition(maxPosition)) + .map(Car::getName) + .collect(Collectors.toList()); + } + + private int findMaxPosition() { + return racers.stream() + .mapToInt(Car::getPosition) + .max() + .orElse(0); + } + + public List getRacers() { + return new ArrayList<>(racers); + } + + @Override + public String toString() { + return racers.stream() + .map(Car::toString) + .collect(Collectors.joining("\n")); + } +} \ No newline at end of file From c99e3b189b0d660fbe5e7dcc103f4460e8e9cd48 Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 12:56:21 +0900 Subject: [PATCH 06/13] =?UTF-8?q?feature:=20result=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=A5=BC=20=EB=8B=B4=EA=B3=A0=EC=9E=88=EB=8A=94=20dto=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/dto/RoundResultDto.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/racingcar/dto/RoundResultDto.java diff --git a/src/main/java/racingcar/dto/RoundResultDto.java b/src/main/java/racingcar/dto/RoundResultDto.java new file mode 100644 index 0000000..5d0e22b --- /dev/null +++ b/src/main/java/racingcar/dto/RoundResultDto.java @@ -0,0 +1,15 @@ +package racingcar.dto; + +import java.util.List; + +public class RoundResultDto { + private final List carProgress; + + public RoundResultDto(List carProgress) { + this.carProgress = carProgress; + } + + public String formatResult() { + return String.join("\n", carProgress); + } +} From da2bfa65240b70adac50db5779e08195d432cccb Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 12:56:32 +0900 Subject: [PATCH 07/13] =?UTF-8?q?feature:=20main=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/Application.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e..b41389a 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,17 @@ package racingcar; +import racingcar.domain.RaceManager; +import racingcar.view.ConsoleView; +import racingcar.view.View; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + try { + View view = new ConsoleView(); + RaceManager raceManager = new RaceManager(view); + raceManager.start(); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } } -} +} \ No newline at end of file From 86432502d665c32540cb317c0a2a72eb9b6a4605 Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 13:17:07 +0900 Subject: [PATCH 08/13] =?UTF-8?q?refactor:=20=EC=A0=95=EC=A0=81=20?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/domain/car/Car.java | 9 +++++++-- src/main/java/racingcar/domain/car/CarName.java | 10 +++++++--- src/main/java/racingcar/domain/car/Cars.java | 6 +++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/java/racingcar/domain/car/Car.java b/src/main/java/racingcar/domain/car/Car.java index d7c8782..94fdd42 100644 --- a/src/main/java/racingcar/domain/car/Car.java +++ b/src/main/java/racingcar/domain/car/Car.java @@ -11,10 +11,15 @@ public Car(CarName name, int position) { this.position = position; } - public void advance(MovementStrategy strategy) { + public static Car create(CarName name, int position) { + return new Car(name, position); + } + + public Car move(MovementStrategy strategy) { if (strategy.shouldMove()) { - position++; + return new Car(this.name, this.position + 1); } + return this; } public boolean isAtPosition(int position) { diff --git a/src/main/java/racingcar/domain/car/CarName.java b/src/main/java/racingcar/domain/car/CarName.java index 983003e..e009e77 100644 --- a/src/main/java/racingcar/domain/car/CarName.java +++ b/src/main/java/racingcar/domain/car/CarName.java @@ -5,12 +5,16 @@ public class CarName { private final String value; private static final int NAME_LIMIT_LENGTH = 5; - public CarName(String value) { - validateName(value); + private CarName(String value) { this.value = value; } - private void validateName(String name) { + public static CarName from(String name) { + validateName(name); + return new CarName(name); + } + + private static void validateName(String name) { if (name.length() > NAME_LIMIT_LENGTH) { throw new IllegalArgumentException(NAME_ERROR.toString()); } diff --git a/src/main/java/racingcar/domain/car/Cars.java b/src/main/java/racingcar/domain/car/Cars.java index 7e8c9a6..a4d4e3d 100644 --- a/src/main/java/racingcar/domain/car/Cars.java +++ b/src/main/java/racingcar/domain/car/Cars.java @@ -12,12 +12,12 @@ public class Cars { public Cars(List names) { this.racers = names.stream() - .map(name -> new Car(new CarName(name), INITIAL_POSITION)) - .collect(Collectors.toList()); + .map(name -> Car.create(CarName.from(name), INITIAL_POSITION)) + .collect(Collectors.toUnmodifiableList()); } public void moveAll(MovementStrategy strategy) { - racers.forEach(car -> car.advance(strategy)); + racers.forEach(car -> car.move(strategy)); } public List findWinnerNames() { From fb1980bf9c8c63d227d60fa02967e9d58aba9b97 Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 13:17:17 +0900 Subject: [PATCH 09/13] =?UTF-8?q?refactor:=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/Application.java | 17 +++++++++++++++-- src/main/java/racingcar/domain/RaceManager.java | 4 ++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e..c10ab32 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,20 @@ package racingcar; +import racingcar.domain.RaceManager; +import racingcar.domain.strategy.MovementStrategy; +import racingcar.domain.strategy.RandomMovement; +import racingcar.view.ConsoleView; +import racingcar.view.View; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + try { + View view = new ConsoleView(); + MovementStrategy strategy=new RandomMovement(); + RaceManager raceManager = new RaceManager(view,strategy); + raceManager.start(); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } } -} +} \ No newline at end of file diff --git a/src/main/java/racingcar/domain/RaceManager.java b/src/main/java/racingcar/domain/RaceManager.java index 0a8037c..390770d 100644 --- a/src/main/java/racingcar/domain/RaceManager.java +++ b/src/main/java/racingcar/domain/RaceManager.java @@ -14,9 +14,9 @@ public class RaceManager { private final MovementStrategy movementStrategy; private Cars cars; - public RaceManager(View view) { + public RaceManager(View view, MovementStrategy movementStrategy) { this.view = view; - this.movementStrategy = new RandomMovement(); + this.movementStrategy = movementStrategy; } public void start() { From 62c1d567f61433de68756ef1fd3e95391744b63b Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 13:25:40 +0900 Subject: [PATCH 10/13] =?UTF-8?q?fix=20:=20=EC=BB=A4=EB=B0=8B=20=EC=8B=9C?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=EB=90=9C=20=ED=8C=8C=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/Application.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e..d8b2110 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,21 @@ package racingcar; +import racingcar.domain.RaceManager; +import racingcar.domain.strategy.MovementStrategy; +import racingcar.domain.strategy.RandomMovement; +import racingcar.view.ConsoleView; +import racingcar.view.View; + public class Application { public static void main(String[] args) { + try { + View view = new ConsoleView(); + MovementStrategy strategy=new RandomMovement(); + RaceManager raceManager = new RaceManager(view,strategy); + raceManager.start(); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } // TODO: 프로그램 구현 } } From 97b4ec856cfff6fc7cfc89ec2bed724d017b4b8c Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 13:25:53 +0900 Subject: [PATCH 11/13] =?UTF-8?q?fix=20:=20=EC=BB=A4=EB=B0=8B=20=EC=8B=9C?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=EB=90=9C=20=ED=8C=8C=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=952?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/Application.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index d8b2110..cb95372 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -16,6 +16,5 @@ public static void main(String[] args) { } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); } - // TODO: 프로그램 구현 } } From 04457b6822772ecf1c62150a5c97ed0186b157c7 Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 13:37:44 +0900 Subject: [PATCH 12/13] =?UTF-8?q?refactor=20:=20method=20name=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/domain/car/Car.java | 4 ++-- src/main/java/racingcar/domain/car/Cars.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/racingcar/domain/car/Car.java b/src/main/java/racingcar/domain/car/Car.java index 94fdd42..332449c 100644 --- a/src/main/java/racingcar/domain/car/Car.java +++ b/src/main/java/racingcar/domain/car/Car.java @@ -26,11 +26,11 @@ public boolean isAtPosition(int position) { return this.position == position; } - public int getPosition() { + public int displayPosition() { return position; } - public String getName() { + public String displayName() { return name.toString(); } diff --git a/src/main/java/racingcar/domain/car/Cars.java b/src/main/java/racingcar/domain/car/Cars.java index a4d4e3d..59c81b1 100644 --- a/src/main/java/racingcar/domain/car/Cars.java +++ b/src/main/java/racingcar/domain/car/Cars.java @@ -25,13 +25,13 @@ public List findWinnerNames() { return racers.stream() .filter(car -> car.isAtPosition(maxPosition)) - .map(Car::getName) + .map(Car::displayName) .collect(Collectors.toList()); } private int findMaxPosition() { return racers.stream() - .mapToInt(Car::getPosition) + .mapToInt(Car::displayPosition) .max() .orElse(0); } From 9aeafbc64fd30eb904e69cb38dec038566292cc5 Mon Sep 17 00:00:00 2001 From: Suehyun666 Date: Wed, 9 Apr 2025 17:58:37 +0900 Subject: [PATCH 13/13] =?UTF-8?q?docs=20:=20=EB=AC=B8=EC=84=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 316 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 316 insertions(+) diff --git a/docs/README.md b/docs/README.md index e69de29..cd1ca15 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,316 @@ +# 이펙티브 자바 원칙을 적용한 자동차 경주 게임 설계 + +## 목차 +1. [프로젝트 소개](#1-프로젝트-소개) +2. [적용한 이펙티브 자바 원칙](#2-적용한-이펙티브-자바-원칙) +3. [객체지향 설계 원칙 적용](#3-객체지향-설계-원칙-적용) +4. [설계 개선 과정](#4-설계-개선-과정) +5. [테스트 용이성](#5-테스트-용이성) +6. [결론 및 추가 개선 가능성](#6-결론-및-추가-개선-가능성) + +## 1. 프로젝트 소개 + +### 자동차 경주 게임 요구사항 +- 0~9 사이의 무작위 값이 4 이상일 경우 자동차가 전진 +- 자동차별로 이름 부여 가능 (최대 5자) +- 사용자 입력으로 자동차 이름 및 시도 횟수 결정 +- 경주 완료 후 가장 멀리 이동한 자동차를 우승자로 선정 + +### 프로젝트 구조 +``` +racingcar +├── domain # 핵심 도메인 객체 +│ ├── car # 자동차 관련 (Car, CarName, Cars) +│ └── strategy # 전략 관련 (MovementStrategy, RandomMovement) +├── dto # 데이터 전송 객체 (RoundResultDto) +├── view # 뷰 관련 (View, ConsoleView) +└── constants # 상수 관련 (ErrorMessage) +``` + +## 2. 적용한 이펙티브 자바 원칙 + +### 아이템 1: 생성자 대신 정적 팩터리 메서드를 고려하라 + +**CarName 클래스에 적용:** +```java +public class CarName { + private final String value; + private static final int NAME_LIMIT_LENGTH = 5; + + private CarName(String value) { // private 생성자 + this.value = value; + } + + public static CarName from(String name) { // 정적 팩터리 메서드 + validateName(name); + return new CarName(name); + } + + // ... +} +``` + +**Car 클래스에 적용:** +```java +public class Car { + // ... + + public static Car create(CarName name, int position) { + return new Car(name, position); + } + + // ... +} +``` + +**적용 이유:** +- 메서드 이름을 통해 객체 생성 의도를 명확히 전달 +- 호출할 때마다 새 객체 생성이 필요 없음 (캐싱 가능) +- 하위 타입 객체 반환 가능성 제공 +- 정적 팩터리 메서드 파라미터에 따라 다양한 생성 방식 지원 + +**얻은 효과:** +- 코드 가독성과 의도 표현력 향상 +- 객체 생성 로직 캡슐화 +- 확장 가능성 확보 + +### 아이템 17: 변경 가능성을 최소화하라 + +**CarName을 불변 클래스로 설계:** +```java +public final class CarName { // 상속 방지 + private final String value; // 불변 필드 + + // ... + + @Override + public String toString() { + return value; + } +} +``` + +**Car에서 불변성 활용:** +```java +public Car move(MovementStrategy strategy) { + if (strategy.shouldMove()) { + return new Car(this.name, this.position + 1); // 새 객체 반환 + } + return this; +} +``` + +**적용 이유:** +- 불변 객체는 단순하고 스레드 안전함 +- 상태 변경 시 새로운 객체를 생성하여 부수효과 방지 +- 불변 객체는 공유하기 쉬움 + +**얻은 효과:** +- 예측 가능한 동작으로 버그 감소 +- 동시성 문제 사전 방지 +- 방어적 복사본이 필요 없어 성능 향상 + +### 아이템 5: 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 + +**RaceManager 클래스에 적용:** +```java +public class RaceManager { + private final View view; + private final MovementStrategy movementStrategy; + private Cars cars; + + public RaceManager(View view, MovementStrategy movementStrategy) { + this.view = view; + this.movementStrategy = movementStrategy; + } + + // ... +} +``` + +**Application 클래스에서 의존성 주입:** +```java +public class Application { + public static void main(String[] args) { + try { + View view = new ConsoleView(); + MovementStrategy strategy = new RandomMovement(); + RaceManager raceManager = new RaceManager(view, strategy); + raceManager.start(); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } +} +``` + +**적용 이유:** +- 테스트 용이성 확보 (테스트에서 다른 전략 주입 가능) +- 유연성 향상 (다양한 구현체로 쉽게 교체 가능) +- 싱글턴이나 정적 유틸리티 클래스의 단점 회피 + +**얻은 효과:** +- 낮은 결합도 확보 +- 테스트 가능한 코드 작성 +- 각 컴포넌트의 책임 명확화 + +### 아이템 12: toString을 항상 재정의하라 + +**Car 클래스의 toString 재정의:** +```java +@Override +public String toString() { + return name.toString() + " : " + "-".repeat(position); +} +``` + +**CarName 클래스의 toString 재정의:** +```java +@Override +public String toString() { + return value; +} +``` + +**적용 이유:** +- 객체의 상태를 명확하게 표현 +- 디버깅 및 로깅 용이성 향상 +- 가독성 있는 출력 제공 + +**얻은 효과:** +- 디버깅 시간 단축 +- 코드 가독성 향상 +- 객체 상태 파악이 쉬움 + +## 3. 객체지향 설계 원칙 적용 + +### getter/setter 지양 및 Tell, Don't Ask 원칙 + +**행동 중심 메서드 설계:** +```java +// getter 대신 행동 중심 메서드 사용 +public boolean isAtPosition(int position) { + return this.position == position; +} + +// 외부에서 상태 확인 후 판단하는 대신 객체에게 판단 위임 +public boolean hasTraveledFartherThan(Car other) { + return this.position > other.position; +} +``` + +**적용 이유:** +- 객체의 캡슐화 강화 +- 객체의 자율성 증진 +- 데이터 중심이 아닌 행동 중심 설계 지향 + +**얻은 효과:** +- 객체 간 결합도 감소 +- 코드의 응집도 향상 +- 객체의 책임과 역할 명확화 + +### 캡슐화와 정보 은닉 + +**상태 검증 로직 캡슐화:** +```java +private static void validateName(String name) { + if (name.length() > NAME_LIMIT_LENGTH) { + throw new IllegalArgumentException(NAME_ERROR.toString()); + } +} +``` + +**내부 구현 은닉:** +```java +// Cars 클래스에서 racers 리스트 방어적 복사 +public List getRacers() { + return new ArrayList<>(racers); +} +``` + +**적용 이유:** +- 구현 세부사항 은닉 +- 변경 영향 범위 최소화 +- 객체 내부 상태 보호 + +**얻은 효과:** +- 유지보수성 향상 +- 코드 변경의 영향 범위 제한 +- 버그 발생 가능성 감소 + +## 4. 설계 개선 과정 + +### 초기 설계의 문제점 +- 도메인 객체와 뷰 간 직접적인 결합 +- getter/setter 남용으로 인한 캡슐화 약화 +- 전략 패턴 미적용으로 인한 확장성 부족 + +### 개선된 설계 +- 패키지 구조 개선으로 관심사 분리 +- 도메인 모델과 뷰 사이에 DTO 도입 +- 의존성 주입을 통한 결합도 감소 +- 불변 객체 도입으로 상태 관리 안정화 + +**DTO를 통한 계층 분리:** +```java +public class RoundResultDto { + private final List carProgress; + + public RoundResultDto(List carProgress) { + this.carProgress = carProgress; + } + + public String formatResult() { + return String.join("\n", carProgress); + } +} +``` + +## 5. 테스트 용이성 + +### 전략 패턴을 활용한 테스트 +```java +// 테스트용 전략 구현체 +public class AlwaysMoveStrategy implements MovementStrategy { + @Override + public boolean shouldMove() { + return true; + } +} + +// 테스트 코드 +@Test +void carShouldMoveWhenStrategyReturnsTrue() { + Car car = Car.create(CarName.from("test"), 0); + Car movedCar = car.move(new AlwaysMoveStrategy()); + + assertEquals(1, movedCar.getPosition()); +} +``` + +### 의존성 주입을 통한 격리된 테스트 +```java +@Test +void raceShouldFindCorrectWinner() { + // 테스트용 뷰와 전략 주입 + View testView = new TestView(); + MovementStrategy testStrategy = new PredictableStrategy(); + + RaceManager manager = new RaceManager(testView, testStrategy); + // 테스트 실행 및 검증... +} +``` + +## 6. 결론 및 추가 개선 가능성 + +### 적용한 이펙티브 자바 원칙 요약 +- 아이템 1: 생성자 대신 정적 팩터리 메서드를 고려하라 +- 아이템 5: 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 +- 아이템 12: toString을 항상 재정의하라 +- 아이템 17: 변경 가능성을 최소화하라 + +### 추가 적용 가능한 원칙들 +- 아이템 11: equals를 재정의하려거든 hashCode도 재정의하라 +- 아이템 34: int 상수 대신 열거 타입을 사용하라 +- 아이템 50: 적시에 방어적 복사본을 만들라 +- 아이템 54: null이 아닌, 빈 컬렉션이나 배열을 반환하라