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

[자동자 경주 게임] 이준섭 미션 제출합니다. #533

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
121 changes: 121 additions & 0 deletions src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,128 @@
package racingcar;

import camp.nextstep.edu.missionutils.Console;

import java.util.*;

public class Application {
public static void main(String[] args) {
// TODO 구현 진행
// 자동차 이름 입력 -> 유효성 검사
List<String> carNames = getCarNames();
// 자동차 목록 생성
List<Car> cars = createCars(carNames);
// 시도 횟수 입력
int tryCount = getTryCount();
// 레이싱 게임 실행 -> 한 라운드 결과 출력
runRacingGame(cars, tryCount);
// 우승자 목록 찾기 -> 우승자 목록 출력
printWinners(findWinners(cars));
Copy link

Choose a reason for hiding this comment

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

메서드 이름이 명확해서 굳이 주석이 없어도 될 것 같지 않나요?🤔

Copy link
Author

Choose a reason for hiding this comment

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

네 맞습니다!
함수 이름을 정하기 전에 구현해야할 목록을 주석으로 만들어둔 후 이름을 정했기 때문에 남아있습니다!

}

// 자동차 이름 입력
private static List<String> getCarNames() {
// Scanner scanner = new Scanner(System.in);
Copy link

Choose a reason for hiding this comment

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

안쓰는 코드 삭제해주세요!

Copy link
Author

Choose a reason for hiding this comment

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

네!

List<String> carNames;
while (true) {
System.out.println("경주할 자동차 이름을 입력하세요. (이름은 쉼표(,) 기준으로 구분)");
String input = Console.readLine();
carNames = Arrays.asList(input.split(",", -1));
try { // 예외가 발생한다면? -> catch
validateCarNames(carNames); // 유효성 검사
break;
}
catch (IllegalArgumentException e) {
System.out.println("[ERROR] 올바른 형식의 자동차 이름을 입력하세요.");
}
}
return carNames;
}

// 자동차 이름 유효성 검사
private static void validateCarNames(List<String> carNames) {
Copy link

Choose a reason for hiding this comment

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

자동차의 이름을 검사하는 기능은 Application 클래스의 책임일까요? 아니면 Car 클래스의 책임일까요?

Copy link
Author

Choose a reason for hiding this comment

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

생각해보지 못한 내용입니다.
처음 봤을 때는 Application 클래스의 책임이라고 생각습니다. Car 객체는 정보만을 담고 있고 추가적인 기능들은 Application 단계에서 진행된다고 생각했기 때문입니다.

하지만 Car 클래스의 책임으로 간주되는 군요.
객체 지향 프로그래밍에서 각 클래스가 자체적으로 데이터를 처리해야하며 이어져 캡슐화하여 객체 데이터를 보호하는 것이 중요합니다.
Application 클래스는 전체 프로그램의 흐름을 제어하고, 사용자 입력을 처리하는 역할입니다.
자동차 이름을 입력 받은 후 Car 객체로 만드는 역할을 Application 에서 하며 Car 객체가 이름 유효성을 검사하도록 하면 이후 추가적인 클래스가 생겼을 때 각 클래스가 자신의 역할을 분담하고 유지보수와 확장에 용이합니다.

for (String str : carNames) {
if (str.isEmpty() || str.length() > 5) {
throw new IllegalArgumentException();
}
}
Copy link

Choose a reason for hiding this comment

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

변수명으로 str보다 더 적합한 이름을 붙여줘도 좋을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

Application에서 carNames을 이미 사용하고 있고, Car 클래스에도 멤버변수로 name이 있습니다.
여기에서는 입력된 자동차 이름을 나타내는 것이니까 inputName으로 변경한다면 보다 이해하기 쉬운 명확한 코드가 되리라 생각합니다.

Copy link

Choose a reason for hiding this comment

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

👍👍👍 이렇게 본인의 생각 잘 전달해주시는 게 리뷰할 때 많이 도움이 됩니다.

Set<String> uniqueNames = new HashSet<>(carNames); // 중복을 허용하지 않는 자료구조
if (uniqueNames.size() < carNames.size()) { // 작다면 중복된 이름이 있다는 것을 의미함
throw new IllegalArgumentException();
}
Copy link

Choose a reason for hiding this comment

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

적절한 자료구조 활용💯

Copy link
Author

Choose a reason for hiding this comment

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

HashSet은 중복을 허용하지 않는 집합을 나타내는 자료구조입니다.
if (uniqueNames.size() < carNames.size()) {
-> 중복을 허용하지 않는 HashSet은 중복된 값들을 하나로 처리하기 때문에 크기가 작다면 중복된 값이 있다는 뜻입니다.

}

// 시도 횟수 입력
private static int getTryCount() {
Copy link

Choose a reason for hiding this comment

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

일반적으로 get으로 시작하는 메서드를 자바에서는 getter 라고 부릅니다.
보통 getter의 역할을 클래스의 필드를 그대로 반환하는 것이라고 보는 개발자들이 많아요.
ex)

class Car {
    String name;

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

    public String getName() {
        return this.name;
    }
}

물론 팀의 약속에 따라 달라질 수 있겠지만, 우리는 다른 개발자들과 협업해야하기 때문에 다른 개발자들이 익숙한 방식으로 코드를 작성함으로 협업의 효율을 높일 수도 있어요.

그럼 이 메서드 이름을 어떻게 수정해볼 수 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

입력 받는 메서드니까 receive로 바꿔보았습니다!

Copy link

Choose a reason for hiding this comment

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

좋습니다! 사소한 것 같지만 나중에 코드가 복잡해지고 여러 사람과 협업하게 될때 이런 규칙을 미리 만들고 가면 장점이 많다는 거 기억해두시면 좋을 것 같네요!

int tryCount;
while (true) {
System.out.println("시도할 횟수는 몇 회인가요?");
String input = Console.readLine();
try {
tryCount = Integer.parseInt(input); // 문자열을 정수로 변환하는 문법
if (tryCount <= 0) {
throw new IllegalArgumentException();
}
break;
Copy link

Choose a reason for hiding this comment

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

문제에 이런 요구사항이 있었어요

indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.

이 코드는 while에서 1번, try에서 2번, if에서 3번 해서 총 3 들여쓰기를 가지고 있네요. 수정해볼 수 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

좋은 지적 감사합니다!
메서드를 분리해야겠군요! validateTryCount()를 새로 만들었습니다!

}
catch (IllegalArgumentException e) {
System.out.println("[ERROR] 올바른 형식의 숫자를 입력하세요.");
}
}
return tryCount;
}

// 자동차 목록 생성.
private static List<Car> createCars(List<String> carNames) {
List<Car> cars = new ArrayList<>(); // 리스트 생성 후 리스트의 원소가 Car 크랫스의 인스턴스가 되도록
for (String name : carNames) {
cars.add(new Car(name));
}
return cars;
}

// 레이싱 게임 실행
private static void runRacingGame(List<Car> cars, int tryCount) {
System.out.println("\n실행 결과");
for (int i = 0; i < tryCount; i++) {
for (Car car : cars) {
car.moveForward();
}
printRoundResult(cars);
}
}

// 한 라운드 결과 출력
private static void printRoundResult(List<Car> cars) {
for (Car car : cars) {
// System.out.println(car.getName() + " : " + "-".repeat(car.getPosition()));
Copy link

Choose a reason for hiding this comment

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

안쓰는 코드 삭제해주세요!22

Copy link
Author

Choose a reason for hiding this comment

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

사용하지 않는 코드는 삭제!

System.out.println(car.getName() + " : " + new String(new char[car.getPosition()]).replace('\0', '-'));
Copy link

Choose a reason for hiding this comment

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

Suggested change
System.out.println(car.getName() + " : " + new String(new char[car.getPosition()]).replace('\0', '-'));
System.out.println(car.name + " : " + new String(new char[car.getPosition()]).replace('\0', '-'));

Car 클래스의 name 필드 접근 제어자를 private에서 public 으로 변경하면 다음과 같이 코드를 수정할 수 있습니다.
그런데 문제에는 이런 요구사항이 있었죠.
name, position 변수의 접근 제어자인 private을 변경할 수 없다.

왜 필드를 private으로 사용해야할까요?

Copy link
Author

@junseoplee junseoplee Jan 1, 2024

Choose a reason for hiding this comment

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

멤버 변수로의 직접 접근은 좋지 않습니다. 객체 지향 프로그래밍의 원칙 중 하나인 캡슐화에 위배됩니다.
캡슐화는 외부에서의 직접 접근을 막고, 객체에 접근하는 메서드를 통해서만 접근이 가능하게 합니다. 정보를 숨겨 안정성을 높이고 유지보수, 재사용에 용이하게 합니다.

Copy link

Choose a reason for hiding this comment

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

왜 외부에서 직접 접근하면 안될까요?

Copy link
Author

Choose a reason for hiding this comment

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

외부에서 직접 접근하는 메서드가 많아진다면 추후 클래스의 구성이나 구현을 변경할 때 수정이 어려워지기 때문입니다!

}
System.out.println();
}

// 우승자 목록 찾기
private static List<String> findWinners(List<Car> cars) {
int maxPosition = getMaxPosition(cars);
List<String> winners = new ArrayList<>();
for (Car car : cars) {
if (car.getPosition() == maxPosition) {
winners.add(car.getName());
}
}
return winners;
}

// 최대 위치 찾기
private static int getMaxPosition(List<Car> cars) {
int maxPosition = 0;
for (Car car : cars) {
maxPosition = Math.max(maxPosition, car.getPosition());
}
return maxPosition;
}

// 우승자 목록 출력
private static void printWinners(List<String> winners) {
System.out.println("최종 우승자 : " + String.join(", ", winners));
Copy link

Choose a reason for hiding this comment

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

적절한 함수활용 굿👍

Copy link
Author

Choose a reason for hiding this comment

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

String.join은 문자열을 결합할 때 사용합니다.
delimiter(,)로 구분하여 요소들을 하나의 문자열로 결합합니다.

}
}
17 changes: 16 additions & 1 deletion src/main/java/racingcar/Car.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package racingcar;

import camp.nextstep.edu.missionutils.Randoms;

public class Car {
private final String name;
private int position = 0;
Copy link

Choose a reason for hiding this comment

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

  1. int와 Integer의 차이는 뭘까요?
  2. 이렇게 멤버 변수를 선언하며 직접 초기화 해주는 것과 생성자에서 초기화 해주는 것 어떤 차이가 있을까요?

생성자 예시

public Car(String name) {
    this.name = name;
    this.position = 0;
}

Copy link
Author

@junseoplee junseoplee Dec 31, 2023

Choose a reason for hiding this comment

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

  1. int와 integer의 차이
    -> int (기본 자료형)
    int는 기본적인 정수 데이터 타입으로, 자바의 8가지 기본 데이터 타입 중 하나입니다.
    int는 정수 값을 저장하며, 32비트로 표현됩니다.
    null 값이나 기타 메서드를 호출할 수 없습니다.
    -> Integer (래퍼 클래스)
    Integer는 int의 래퍼 클래스로, 정수를 객체로 감싸는 역할을 합니다.
    객체 지향적인 프로그래밍에서 int를 객체로 다룰 수 있도록 만든 클래스입니다.

Integer 클래스는 int의 기능을 확장하여 여러 유용한 메서드를 제공합니다.
null을 포함한 모든 객체의 메서드를 호출할 수 있습니다.

어떤 메서드들이 있을까?
해당 과제에서 사용했던 Integer.parseInt(input); 를 예로 들어 문자열을 정수로 변환하는 등 여러 메서드를 사용할 수 있습니다.

  1. 멤버 변수를 선언하며 직접 초기화 하는 것과 생성자에서 초기화하는 것의 차이
    직접 초기화 -> 해당 클래스의 모든 인스턴스가 같은 초기값을 가집니다.
    생성자에서 초기화 -> 객체를 초기화 할 때. 즉, 객체를 생성할 때 마다 서로 다른 초기값을 설정할 수 있습니다.

Copy link

@poi1649 poi1649 Jan 2, 2024

Choose a reason for hiding this comment

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

정리 좋네요!
2번에서 한 가지만 제 의견을 말씀드리자면, 저는 멤버 변수를 초기화하는 로직이 여기저기 퍼져있을 때 찾기 힘들어지는 경우가 생기기도 하는 것 같아요.(코드가 복잡해질수록 더더욱)
때문에 웬만한 경우 생성자에서 모든 멤버 변수를 초기화 하는 방법으로 사용합니다.
이런 부분에 대해 매번 고민하는 것도 자원 낭비라고 생각하기 때문에 이 기회에 본인만의 규칙을 세워봐도 좋겠네요!

Copy link
Author

Choose a reason for hiding this comment

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

그렇군요! 멤버 변수는 생성자에서 초기화 하도록 해야겠습니다!

Expand All @@ -8,5 +10,18 @@ public Car(String name) {
this.name = name;
}

// 추가 기능 구현
Copy link

Choose a reason for hiding this comment

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

필요없는 주석삭제 👍👍

Copy link
Author

Choose a reason for hiding this comment

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

필요 없는 주석은 삭제!

public void moveForward() {
int randomValue = Randoms.pickNumberInRange(0,9);
if (randomValue >= 4) {
position++;
}
Comment on lines +17 to +20
Copy link

Choose a reason for hiding this comment

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

이렇게 바꾸기 vs 그냥 쓰기
각각 어떤 장단점이 있을까요?

Suggested change
int randomValue = Randoms.pickNumberInRange(0,9);
if (randomValue >= 4) {
position++;
}
if (Randoms.pickNumberInRange(0,9) >= 4) {
position++;
}

Copy link
Author

@junseoplee junseoplee Dec 31, 2023

Choose a reason for hiding this comment

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

바꿨을 때의 장단점

장점:
코드가 간결해집니다. 한 번 사용되는 간단한 로직일 경우에, 중복 코드를 최소화할 수 있습니다.
단점:
매번 Randoms.pickNumberInRange를 호출하기 때문에 성능상 단점이 있을 수 있습니다.
코드 한 줄에 많은 동작이 있다면 가독성이 감소할 수 있습니다. 복잡하고 다양한 동작을 수행할 경우에 이해하기 어려워질 수 있습니다.
이어져 코드가 복잡해지면 유지보수가 어려워질 수 있습니다.

randomValue 변수를 사용한다면 코드를 읽기가 더 쉽습니다. 다른 부분에서 동일한 난수를 사용한다면 재사용에 용이합니다. 또한 난수 생성 로직이 변경되더라도 해당 부분만 수정하면 됩니다.

Copy link

Choose a reason for hiding this comment

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

정리 깔끔하네요! 그런데 매번 호출하는건 위 아래 전부 동일에서 성능은 사실상 동일합니다!
현재 코드에선 각각 가독성, 코드 길이에서 �우위가 있겠네요!

Copy link
Author

Choose a reason for hiding this comment

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

맞습니다! 재사용할 수 있다는 것에 초점을 두고 이런 답변을 드렸네요! 현재 코드에서 성능은 동일합니다!

}

public String getName() {
return name;
}

public int getPosition() {
return position;
}
}