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
115 changes: 114 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,120 @@
package racingcar;

import camp.nextstep.edu.missionutils.Console;

import java.util.*;

public class Application {
public static void main(String[] args) {
// TODO 구현 진행
List<String> carNames = receiveCarNames();

List<Car> cars = createCars(carNames);

int tryCount = receiveTryCount();

runRacingGame(cars, tryCount);

printWinners(findWinners(cars));
}


private static List<String> receiveCarNames() {
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 inputName : carNames) {
if (inputName.isEmpty() || inputName.length() > 5) {
Copy link

Choose a reason for hiding this comment

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

Suggested change
if (inputName.isEmpty() || inputName.length() > 5) {
if (inputName.isEmpty() || inputName.length() > MAX_NAME_LENGTH) {

이렇게 클래스에 상수를 따로 선언해서 수정했을 때 어떤 이점이 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

코드가 길고 복잡해졌을 때 숫자 5가 무엇을 나타내는지 혼동이 올 수 있습니다.
이렇게 따로 상수를 선언했을 경우 보다 명확하고 이해하기 쉬워집니다.
또한 추후 값을 변경해야할 경우에 5를 각각 수정하는 것보다 선언해둔 상수를 수정하는 것이 편리하기 때문에 유지보수가 용이하다는 이점이 있습니다.

throw new IllegalArgumentException();
}
}
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 receiveTryCount() {
int tryCount;
while (true) {
System.out.println("시도할 횟수는 몇 회인가요?");
String input = Console.readLine();
try {
tryCount = Integer.parseInt(input); // 문자열을 정수로 변환하는 문법
validateTryCount(tryCount);
break;
}
catch (IllegalArgumentException e) {
System.out.println("[ERROR] 올바른 형식의 숫자를 입력하세요.");
}
}
return tryCount;
}

private static void validateTryCount(int tryCount) {
if (tryCount <= 0) {
throw new IllegalArgumentException();
}
}

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() + " : " + 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;
}
}