Skip to content

[자동차 경주 게임] 박세민 미션 제출합니다.#5

Open
semInDev wants to merge 15 commits intowoowa-precourse-study:mainfrom
semInDev:semInDev
Open

[자동차 경주 게임] 박세민 미션 제출합니다.#5
semInDev wants to merge 15 commits intowoowa-precourse-study:mainfrom
semInDev:semInDev

Conversation

@semInDev
Copy link
Member

1. 구현 기능 목록

1. 입력 기능

  • 자동차 이름 입력받기
    • 사용자로부터 쉼표(,)로 구분된 자동차 이름 문자열 입력받기
    • 입력 메시지: "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"
  • 시도 횟수 입력받기
    • 사용자로부터 경주 진행 횟수 입력받기
    • 입력 메시지: "시도할 회수는 몇회인가요?"

2. 입력 검증 기능

  • 자동차 이름 검증하기
    • 이름이 5자 이하인지 검증
    • 공백 또는 빈 문자열 검증
    • 검증 실패 시 IllegalArgumentException 발생
  • 시도 횟수 검증하기
    • 숫자 형식인지 검증 (NumberFormatException 처리)
    • 음수가 아닌지 검증
    • 검증 실패 시 IllegalArgumentException 발생

3. 자동차 도메인 기능

  • 자동차 객체 생성하기
    • 자동차 이름과 초기 위치(0) 설정
    • 자동차 이름은 불변값으로 관리
  • 자동차 이동 기능 구현하기
    • 0~9 사이 랜덤 값 생성
    • 랜덤 값이 4 이상일 때 전진 (위치 +1)
    • 4 미만일 때는 정지 (위치 변화 없음)

4. 게임 진행 기능

  • 경주 게임 실행하기
    • 입력받은 횟수만큼 라운드 반복
    • 각 라운드에서 모든 자동차에 대해 이동 판정
    • 라운드별 자동차 상태를 히스토리로 저장
  • 우승자 계산하기
    • 최종 라운드에서 가장 멀리 이동한 자동차 찾기
    • 동일한 최대 거리의 자동차들을 모두 우승자로 선정

5. 출력 기능

  • 경주 진행 과정 출력하기
    • "실행 결과" 메시지 출력
    • 각 라운드별로 모든 자동차의 상태 출력
    • 자동차 이름과 이동 거리를 문자로 표현
    • 라운드 간 빈 줄로 구분
  • 최종 우승자 출력하기
    • "최종 우승자 : " 메시지와 함께 우승자 이름 출력
    • 우승자가 여러 명인 경우 쉼표(,)로 구분하여 출력

6. 전체 애플리케이션 제어 기능

  • 게임 전체 흐름 제어하기
    • 입력 → 검증 → 게임 실행 → 결과 출력 순서로 진행
    • 예외 발생 시 애플리케이션 종료

2. 기술적 구현 사항

객체지향 설계

  • MVC 패턴 적용
    • Controller: 전체 흐름 제어
    • View: 입력/출력 담당
    • Service: 비즈니스 로직 처리
    • Domain: 핵심 도메인 모델
  • 전략 패턴 적용
    • Mover 인터페이스로 이동 로직 추상화
    • RandomMover로 랜덤 기반 이동 구현

데이터 관리

  • 불변 객체 활용
    • 자동차 이름은 생성 후 변경 불가
    • 각 라운드 상태를 스냅샷으로 보존
  • 컬렉션 활용
    • List를 활용한 자동차 목록 관리
    • 2차원 List로 라운드별 히스토리 관리

@semInDev
Copy link
Member Author

semInDev commented Sep 27, 2025

컴포넌트 설명

Controller Layer

  • RacingCarController: 전체 게임 흐름 제어

Domain Layer

  • Car: 자동차 엔티티 (이름, 위치 관리)
  • Mover: 이동 로직 추상화
  • RandomMover: 랜덤 기반 이동 구현
  • CarNameValidator: 자동차 이름 검증 및 Car 객체 생성
  • RoundValidator: 라운드 수 검증
  • WinnerCalculator: 우승자 계산 로직

Service Layer

  • RacingCarService: 경주 진행 및 히스토리 관리

View Layer

  • InputView: 사용자 입력 처리
  • ResultView: 경주 진행상황 및 결과 출력

신경 쓴 부분

객체지향 설계

  • 단일 책임 원칙 (CarNameValidator는 조금.. 애매함)
  • 의존성 역전: Mover 인터페이스를 통한 이동 로직 추상화
  • 불변성: Car의 이름은 불변, 위치만 변경 가능

확장 가능한 구조

  • Strategy 패턴: Mover 인터페이스로 다양한 이동 전략 구현 가능
  • 데이터 저장: 각 라운드별 상태를 히스토리로 관리

Copy link
Collaborator

@khcho96 khcho96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번 주 과제도 수고하셨습니다!:)


public interface Mover {
boolean canMove();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전략 패턴!! 저도 한번 사용해보고 싶네요👍

System.out.println("실행 결과");
for (List<Car> round : raceHistory) {
for (Car car : round) {
System.out.println(car.getCarName() + " : " + "-".repeat(car.getPosition()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 결과값을 문자열에 모두 담아서 출력했는데, Car 객체 안에 레이싱 과정의 모든 정보를 담아서 처리하면 더 좋았을 뻔 했네요!

this.position = position;
}

public String getCarName() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr_checklist.md에 따르면 getter를 쓰지 않고 구현하는 게 좋다고 하는데, 세민님은 어떻게 생각하시는지 궁금합니다. getter 쓰지 않고 머리 굴리려니 힘들더라구요..ㅎ

for (Car car : cars) {
if(maxPosition < car.getPosition()){
maxPosition = car.getPosition();
winners.clear();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약 winners 리스트의 크기가 크다면 O(n)clear 메서드보다 new ArrayList<>()로 상수시간에 처리하면 성능 면에서 더 좋을 것 같아요!

@semInDev
Copy link
Member Author

Strategy Pattern에 대해 더 정리

전략 패턴(Strategy Pattern)행동(알고리즘)을 객체로 캡슐화한 후, 실행 시점에 바꿔 끼울 수 있게 하는 디자인 패턴

  • 동일한 기능을 수행하지만 구현 방법(알고리즘)이 여러 가지 있을 때, 이를 각각 별도의 "전략 객체"로 분리.
  • 컨텍스트(Context, 예: Car)는 전략을 직접 알지 않고 인터페이스만 알고, 실제 알고리즘은 주입된 전략 객체가 결정.
public interface MoveStrategy {
    boolean movable();
}

---

public class RandomMoveStrategy implements MoveStrategy {
    private final Random random = new Random();
    @Override
    public boolean movable() {
        return random.nextInt(10) >= 4;
    }
}

// 나중에 테스트 시에 AlwaysMoveStrategy를 사용하여 테스트를 용이하게 할 수 있음!
public class AlwaysMoveStrategy implements MoveStrategy {
    @Override
    public boolean movable() {
        return true;
    }
}

---
public class Car {
// 컨텍스트인 Car는 Move 전략(구체적인 알고리즘)을 몰라도 됨.
// 실제 알고리즘은 주입된 전략 객체가 결정
    private final String name;
    private int position = 0;
    private final MoveStrategy moveStrategy;

    public Car(String name, MoveStrategy moveStrategy) {
        this.name = name;
        this.moveStrategy = moveStrategy;
    }

    public void move() {
        if (moveStrategy.movable()) {
            position++;
        }
    }
}

1. 장점

  • 유연성: 알고리즘을 실행 중에 쉽게 교체 가능.
    • 예: 자동차를 랜덤 이동 전략 ↔ 항상 이동 전략으로 바꿀 수 있음.
  • OCP(Open-Closed Principle) 준수: 새로운 전략을 추가해도 기존 코드(Car)는 수정할 필요 없음.
  • 테스트 용이: "항상 이동 전략"이나 "절대 안 움직이는 전략"을 넣어 쉽게 테스트 가능.

2. 단점

  • 클래스(전략 객체)가 늘어나 관리가 복잡해질 수 있음.
  • "전략 선택"을 어디서 할지 설계해야 함 (보통 클라이언트나 팩토리).

3. 자동차 경주 게임 예시

  • 문제 상황: 자동차가 움직일지 말지 Random으로 결정해야 함.
  • 전략 패턴 적용 전: Car 내부에서 Random 직접 호출 → 테스트 어려움.
  • 전략 패턴 적용 후: Car는 단순히 MoveStrategy 인터페이스만 사용.
    • RandomMoveStrategy → 실제 게임에서 사용.
    • AlwaysMoveStrategy → 테스트할 때 사용.

@khcho96
Copy link
Collaborator

khcho96 commented Sep 28, 2025

친절한 정리 감사합니다!!!😆👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants