728x90
반응형
기능 요구사항
- 각 자동차에 이름을 부여할 수 있다. 자동차 이름은 5자를 초과할 수 없다.
- 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분한다.
- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4이상일 경우이다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한명 이상일 수 있다.
실행 결과
- 위 요구사항에 따라 3대의 자동차가 5번 움직였을 경우 프로그램을 실행한 결과는 다음과 같다.
경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).
pobi,crong,honux
시도할 회수는 몇회인가요?
5
실행 결과
pobi : -
crong : -
honux : -
pobi : --
crong : -
honux : --
pobi : ---
crong : --
honux : ---
pobi : ----
crong : ---
honux : ----
pobi : -----
crong : ----
honux : -----
pobi : -----
crong : ----
honux : -----
pobi, honux가 최종 우승했습니다.
힌트
- 자동차는 자동차 이름과 위치 정보를 가지는 Car 객체를 추가해 구현한다.
프로그래밍 요구사항
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Google Java Style Guide을 원칙으로 한다.
- 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다.
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
- else 예약어를 쓰지 않는다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
- 3항 연산자를 쓰지 않는다.
- 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
- UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
- 모든 원시 값과 문자열을 포장한다.
- 일급 컬렉션을 쓴다.
기능 요구 사항 정리
- 자동차 이름을 체크한다.
- 자동차 문자열은 , 를 기준으로 구분한다.
- 자동차 이름은 5자를 초과할 수 없다.
- 자동차는 전진할 수 있다.
- 0에서 9사이의 random값을 구할 수 있다.
- 전진하는 조건은 random값이 4이상일 때다.
- 게임 완료 후 우승자를 알려준다.
- 우승자는 한 명 이상일 수 있다.
- 우승자는 한 명 이상일 수 있다.
1. 자동차 객체 (Car)
일급컬렉션을 사용하라
자동차 객체에 들어갈 이름과 위치를 각각 객체화한다.
Name.java
public class Name {
private final String name;
public Name(String name) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("이름은 빈 값이 될 수 없습니다.");
}
if (name.trim().length() > 5) {
throw new IllegalArgumentException("이름은 5자를 초과할 수 없습니다.");
}
this.name = name.trim();
}
public String getName() {
return name;
}
}
Position.java
public class Position {
private int position;
public Position(int position) {
this.position = position;
}
public void move() {
this.position++;
}
public boolean lessThan(Position position) {
return this.position < position.getPosition();
}
public boolean isSame(Position position) {
return this.equals(position);
}
public int getPosition() {
return position;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Position position1 = (Position) o;
return position == position1.position;
}
@Override
public int hashCode() {
return Objects.hash(position);
}
@Override
public String toString() {
return "Position{" + "position=" + position + '}';
}
}
Position과 Name객체에서 validation을 체크하고, 필요한 로직을 구현함으로써 단일책임원칙(Car객체)을 지킨다.
public class Car {
private final Name name;
private final Position position;
public Car(String name) {
this(name, new Position(0));
}
public Car(String name, Position position) {
this.name = new Name(name);
this.position = position;
}
public void move() {
position.move();
}
public Position getMaxposition(Position position) {
if (this.position.lessThan(position)) {
return position;
}
return this.position;
}
public boolean isWinner(Position position) {
return this.position.isSame(position);
}
public String getName() {
return name.getName();
}
public int getPosition() {
return position.getPosition();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Car car = (Car) o;
return Objects.equals(name, car.name) && Objects.equals(position,
car.position);
}
@Override
public int hashCode() {
return Objects.hash(name, position);
}
@Override
public String toString() {
return "Car{"
+ "name='" + name + '\''
+ ", position=" + position + '}';
}
}
Car객체에서는 Name와 Positon객체를 final을 이용해서 불변객체로 만든다.
조금 더 안전한 코드가 된다.
2. 자동차 목록을 가지고 있는 객체
cars.java
public class Cars {
private static final int MOVE_CONDITION = 4;
private static final int MAX_BOUND = 10;
private final List<Car> cars = new ArrayList<>();
public Cars(String names) {
if (StringUtils.isBlank(names)) {
throw new IllegalArgumentException("이름은 빈값이 올 수 없습니다.");
}
for (String name : names.split(",")) {
cars.add(new Car(name));
}
}
public List<Car> getList() {
return this.cars;
}
public void moveCars() {
for (Car car : cars) {
if (movable()) {
car.move();
}
}
}
public int generateRanomNo() {
return new Random().nextInt(MAX_BOUND);
}
public boolean movable() {
return generateRanomNo() > MOVE_CONDITION;
}
public List<Car> findWinners() {
Position position = new Position(0);
for (Car car : cars) {
position = car.getMaxposition(position);
}
List<Car> winnerList = new ArrayList<>();
for (Car car : cars) {
if (car.isWinner(position)) {
winnerList.add(car);
}
}
return winnerList;
}
}
자동차 race중 이동이 필요할 때 체크하는 로직도 분리가능하다.
winner를 찾는 로직에서 getMaxposition에 position객체를 던짐으로 Position객체에서 확인하도록 한다.
3. 자동차 경주 객체
public class RacingCar {
private int tryNo;
private final Cars cars;
public RacingCar(String carNames, int tryNo) {
cars = initCars(carNames);
this.tryNo = tryNo;
}
private Cars initCars(String carNames) {
return new Cars(carNames);
}
public List<Car> race() {
this.tryNo--;
cars.moveCars();
return cars.getList();
}
public List<Car> findWinner() {
return cars.findWinners();
}
public boolean isEnd() {
return tryNo == 0;
}
}
4. 출력객체
public class ResultView {
public void printRace(List<Car> cars) {
for (Car car : cars) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < car.getPosition(); i++) {
sb.append("-");
}
System.out.println(car.getName() + " : " + sb);
}
System.out.println();
}
public void printWinners(List<Car> winners) {
List<String> winnerNames = winners.stream().map(Car::getName).collect(Collectors.toList());
System.out.println(String.join(",", winnerNames) + "가 최종 우승했습니다.");
}
}
view클래스와 실제 행동을 위한 객체는 분리하는 것이 좋다.
정답은 아니다.
728x90
반응형
'TDD이론과연습&리팩토링' 카테고리의 다른 글
Sonarqube 설치 intellij 연동하기 (0) | 2023.02.15 |
---|---|
JUnit5에서 @Test expected 사용법 (0) | 2022.10.26 |
구글 코딩 컨벤션 위반 Rule 수정하기 (0) | 2022.10.19 |
intellij 구글 코딩 컨벤션 적용하기 (0) | 2022.10.19 |
TDD 구현시 체크 사항 (0) | 2022.10.17 |