자동차 경주 미션 회고록

SeokHwan An·2023년 2월 18일
0

우아한테크코스

목록 보기
2/4

우아한 테크코스에 들어오고 나서 처음 마주만 미션은 자동차 경주 미션이었습니다.

자동차 경주 미션은 경주할 자동차 이름과 경주 시도 횟수를 입력받은 후 전진 과정을 통해 가장 많이 전진한 자동차가 우승을 하는 게임으로 이를 동작시키는 프로그램을 구현하는 것이었습니다.

이번 미션을 시작하기에 앞서서 내가 다짐했던 것은 객체를 객체 답게 활용하는 것에 초점을 맞추었고 이를 위해 일급 컬랙션을 적용하는 것과 getter사용을 지양하는 것을 목표로 삼았습니다.

일급 컬랙션


이번 미션 요구사항을 보면서 가장 먼저 떠오른 개념이 일급 컬랙션이었다. 일급 컬랙션은 Collcection Wrapping하면서, Wrapping한 Collection 외 다른 멤버 변수가 없는 상태를 의미했고 이를 이번 미션에 적용하려고 했다. 제가 일급컬렉션을 활용한 이유는 Car클래스를 Collection으로 감싼 객체로 정의해 상태와 행위를 각각 관리하기 위함이었다.

public Class Car {
		
		private String name;
		private int position;

		public Car(String name, int position) {
				...
				this.name;
				this.position;
		}
}

위와 같이 Car 클래스만 생성하는 경우에

  • 자동차 경주는 2대이상부터 시작할 수 있다.

와 같은 요구사항이 추가되는 경우 우리는 보통 RacingGame.class에서 이를 처리하는 방안을 생각할 것이다.

public Class RacingGame {
		...
		public List<Car> generateRacingCar(List<String> carNames) {
				List<Car> cars = new ArrayList<>();
				for(int i = 0; i < carNames.size(); i++) {
						cars.add(new Car(carNmaes.get(i), 0);
				}
				validateRacingCarSize(cars);
				return cars;
		}

		private void validateRacingCarSize(List<Car> cars) {
				if (cars.size() < 2) {
						throw new IllegalArgumentException();
				}
		}
		
		...
		//장애물이라는 객체가 생긴다면 여기서 또 검증을 해주어야하는 것인가??
}

하지만 이런 의문점이 생길 것입니다. 자동차의 개수를 검증하는 기능이 과연 Game 클래스가 담당하는 것이 맞을까?

혹은 나중에 자동차 이외에의 RacingGame에 필요한 객체(ex 장애물)가 요구사항으로 생긴다면 이 역시 Game 클래스에서 처리하면 Game 클래스 내에 중복 코드(혹은 불필요한 코드)가 생기는 것이 아닌가? 하는 의문점을 가지게 될 것이다.

이는 일급 컬랙션으로 해결을 할 수 있다.

public Calss Cars {
		
		private List<Car> cars;

		public Cars(List<Car> cars) {
				validateCarsSize(cars);
        this.cars = new ArrayList<>(cars);
		}
		
		private void validateCarsSize(List<Car> cars) {
        if (cars.size() < CARS_MIN_SIZE) {
            throw new IllegalArgumentException(CARS_SIZE_ERROR);
        }
    }
		//기타 메소드
		//ex) 우승자를 반환하는 메소드
		//ex) 자동차를 이동시키는 메소드
		
}			

일급 컬랙션의 코드는 위와 같고 Cars 자체에서 List를 생성할 때 검증하는 방식으로 Game.class의 책임을 나눌 수 있다.

이 뿐만 아니라 다른 여러 기능에 대해서도

  • ‘자동차 경주 우승자’를 반환하는 기능
  • 자동차를 이동시키는 메소드

등 Car를 이용해 결과값을 도출하는 기능들을 Game.class에서 처리하지 않고 자신 스스로가 처리할 수 있다.

이 부분이 제가 일급 컬랙션을 이용하면서 가장 좋았던 점이었습니다. 단순하게 값을 불러와서 필요한 결과값을 얻기 위한 로직을 구현한 것이 아닌 객체가 직접 가공해서 값을 전달해 주어서 책임 소재 역시 잘 분배되었다는 것을 느낄 수 있었다.

getter를 사용하는 대신 메시지를 던지자


우리는 개발을 하면서 객체지향 프로그래밍이라는 용어를 많이듣는다. 쉽게 말해 객체를 만들고 객체가 서로 상호작용해서 프로그램이 동작하는 것이다. 우리는 객체를 만들고 작업을 하겠지만 객체가 직접 일을 하도록 구성하지 않는 경우가 많다.(getter의 남용) 나 역시도 그랬던 점이 많았다.

public class Car {

    private String name;
    private int position;

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

    public String getName() {
        return name;
    }

    public int getPosition() {
        return position;
    }
}
public class Cars {

    private List<Car> cars;

    public Cars(List<Car> cars) {
        this.cars = cars;
    }

    public List<Car> getCars() {
        return cars;
    }

    private void ValidteCarsSize(List<Car> cars) {
        if (cars.size() < 2) {
            throw new IllegalArgumentException("[ERROR] 자동차는 최소 2대 이상이어야 합니다.");
        }
    }
}

위와 같은 클래스가 있다고 하자 이러면 RacingGame.class에서 우승자를 반환하는 메소드는 이와같이 나올 수 있다.

public Class RacingGame {
		...
		public Car findWinner() {
        return cars.getCars().stream()
            .max(Comparator.comparing(Car::getPosition))
            .get();
    }

}

이러면 우리는 이와 같은 생각을 할 수 있다. 이럴꺼면 Car클래스와 Cars클래스는 역할이 없는 것이 아닌가? 맞다 이는 객체를 만들어 놓고 객체가 아무일도 하지 않게 처리한 것은 필요없는 객체라고 봐도 무방하다. 이를 해결하는 방안은 객체 스스로가 일을 하도록 하고 메세지를 던지는 것이다.

이를 해결하는 방안은 객체에서 스스로 일을 하도록 하는 책임을 지게 하는 것이다.

public class Cars {
		...
    public Car findWinner() {
        return cars.stream()
            .max(Comparator.comparing(Car::getPosition))
            .get();
    }
		...
}

위와 같이 Cars 클래스에서 우승자를 찾는 기능을 책임지게 하면 RacingGame 클래스 내에서 불필요한 getter의 사용을 줄일 수 있고 객체가 일을 하게 된다. 객체가 스스로 일을 하게 되면 어느 한 객체가 무한한 책임을 줄일 수 있고 어는 한 객체에 큰 의존을 할 필요가 없어지게 된다.

getter의 사용을 지양한다고 해서 getter를 무조건 사용하지 말아라는 아니다. console화면에 결과를 출력하기 위해서는 get()를 이용해도 된다. 아니 이용해야지 출력이 가능하다. 여기서 말하는 getter 사용의 지양은 위의 RacingGame.class처럼 비즈니스 로직을 작성하는데 값을 get()으로 가져와서 필요한 결과를 얻는 행위를 하기 보단 관련된 객체가 스스로 일을 하도록해 메세지를 던지도록 하는 것이다.

소감


먼저 프리코스 때는 getter의 사용이 많았고 객체 스스로 일을 하지 않은 경우도 많았는데 이번 미션을 진행할 때에는 객체 스스로가 일을 하다록 구성했던 점이 이번 미션에서 가장 만족스러운 부분이었다. 이 부분은 앞으로의 미션을 진행할 때에도 항상 고민하면서 지켜나가야 할 것 같다.

이번 미션을 진행하면서 항상 좋았던 부분만 있던 것은 아니었다. 고민이 되는 부분 몇가지가 있었다.

  • validation(검증 로직)은 각 객체에서 처리하는 것이 좋을지 아니면 static 클래스로 분리해서 관리하는 것이 좋을지에 대한 고민이 있었다.
  • MVC 패턴의 특징은 Model과 View를 분리했다는 것이 큰 특징인데 View의 메소드에서 인자를 받을 때 객체(ex Car)로 받으면 View의 메소드는 Domain에 의존을 하는 것으로 인식할 수 있는데 이는 그러면 풀어서 값으로 인자로 받는 것이 더 좋은 방안인가에 대한 고민이 있었다.

0개의 댓글