일급 컬렉션 (Fist Class Collection)

이번 첫 코드리뷰를 받으면서 내가 놓치는 부분이 많고, 부족한 부분이 많다는 것을 다시 한 번 느낄 수 있었고, 많은 점을 배울 수 있었다.🥲
피드백 내용을 모두 글로 옮기는 것보다는 링크를 걸어 매번 찾아볼 수 있도록 하는 것이 좋다고 생각되어 링크를 건다!!
(피드백에 대한 나의 생각 혹은 피드백을 수용한 근거를 reply로 제시했기 때문에 나중에 찾아보게 되어도 충분히 상기될 수 있다고 생각된다!!)

자동차 경주 게임(step1) 피드백 PR

그 중에서도 1차 피드백을 받으며 처음 접한 일급 컬렉션 개념에 대해서는 꼭 한 번 정리하고 넘어가는 것이 좋다고 생각되었고, 이렇게 글로 정리하게 되었다.


일급 컬렉션이란?

일급 컬렉션이란 컬렉션을 Wrapping 하면서, Wrapping한 컬렉션 외 다른 멤버 변수가 없는 상태를 말한다.

이렇게 글로만 봐서는 이해가 쉽지 않다. 코드를 통해서 다시 한 번 확인해보자!

public class RacingGame {
	private List<Car> cars;
    ...
}

와 같은 코드에서 List<Car> cars 를 Wrapping하여 다음과 같은 하나의 클래스로 감싸는 것이다.

public class Cars {
	// 여기보면 멤버변수가 Wrapping된 컬렉션 외에는 다른 멤버변수가 없다.
	private List<Car> cars;
    ...
}

일급 컬렉션의 장점

일급 컬렉션에 대해 공부하며 일급 컬렉션의 소개와 써야할 이유 이 블로그 글을 참고하였다.
여기서는 일급 컬렉션의 이점으로 4가지를 이야기한다.

  • 비지니스에 종속적인 자료구조
  • Collection의 불변성을 보장
  • 상태와 행위를 한곳에서 관리
  • 이름이 있는 컬렉션

위 4가지 이점에 대해서 하나하나 나열하는 것은 해당 블로그글을 복사 붙여넣기 하는 그 이상 그 이하도 아니라고 생각되었고, 이번 프리코스에서 일급 컬렉션을 적용하면서 느낀 장점을 위의 언급된 이점과 연관지어서 이야기해보려 한다.


가장 큰 장점

일급 컬렉션을 적용하면서 느낀 가장 큰 장점은 검증과 같이 어떤 특정 조건을 가진 자료구조를 하나의 클랙스로 Wrapping함으로써 해당 검증이 필요하지 않은 곳으로부터 분리해낼 수 있다는 점이다!

예를 들어서 자동차 경주 구현 미션에서는 자동차들(List<Car>)의 이름이 중복되면 안된다는 요구사항이 존재한다. 따라서 이를 검증하는 로직이 필요하게 된다. 이를 아래와 같이 작성하였다고 해보자.

public class RacingGame {

    private List<Car> cars = new ArrayList<>();
    
    ...
    
    private void initRacingCarGame() throws IllegalArgumentException {
        String carNames = getCarNames();
        cars = initCar(carNames);
    }
    
    private static List<Car> initCar(String carNames) throws IllegalArgumentException {
        List<Car> carList = new ArrayList<>();
        String[] names = carNames.split(SEPARATOR);

        validateDuplication(names);

        for (String name : names) {
            carList.add(new Car(name));
        }

        return carList;
    }
}

하지만 모든 자동차 리스트(List<Car>)가 위와 같은 요구사항을 만족해야하지 않을 수도 있다. 그렇다면 어떻게 해서 이를 해결할 수 있을까??

우리가 해당 조건(중복 X)을 가지는 자료구조를 하나 만들어냄으로써 이를 해결할 수 있다! 즉, 자동차(Car)들의 리스트인데 중복이 없는 자동차 리스트 자료구조를 만드는 것이다!! 다음의 코드를 보자.

public class RacingGame {

    private Cars cars;
    
    private void initRacingCarGame() throws IllegalArgumentException {
        String carNames = getCarNames();
        cars = new Cars(carNames);
    }
}

위와 같이 RacingGame을 만들고, 새로운 일급 컬렉션인 Cars를 만드는 것이다!

public class Cars {

    private static final String SEPARATOR = ",";
    private static final int FIRST_INDEX = 0;

    private List<Car> cars;

    public Cars(String carNames) {
        initCar(carNames);
    }

    public int getMaxPosition() {
        cars.sort((o1, o2) -> o2.getPosition() - o1.getPosition());

        return cars.get(FIRST_INDEX).getPosition();
    }

    public List<String> getWinner(int position) {
        return cars.stream()
                .filter(car -> car.isSamePosition(position))
                .map(car -> car.getName())
                .collect(Collectors.toList());
    }

    public void progressWithAllCar() {
        for (Car car : cars) {
            car.progress(getRandomNumber());
        }
    }

    public int getSize() {
        return cars.size();
    }

    public Car getCar(int index) {
        return cars.get(index);
    }

    private void initCar(String carNames) throws IllegalArgumentException {
        cars = new ArrayList<>();
        String[] names = carNames.split(SEPARATOR);

        validateDuplication(names);

        for (String name : names) {
            cars.add(new Car(name));
        }
    }

    private void validateDuplication(String[] carNames) {
        HashSet<String> hashSet = new HashSet<>(Arrays.asList(carNames));

        if (hashSet.size() < carNames.length) {
            throw new IllegalArgumentException("[ERROR] 자동차 이름이 중복되면 안됩니다.");
        }
    }
}

이러면 검증에 대한 로직과 해당 검증이 필요한 데이터를 하나로 묶을 수 있게된다! 이는 곧 상태와 행위를 한 곳에서 관리 와도 일맥 상통하는 이야기라고 보여진다. 그리고 해당 자동차 리스트가 필요한 모든 로직에서는 이 일급 컬렉션만 가져다가 쓰면 된다!


다음으로 느낀 장점

컬렉션의 불변을 보장한다는 것이 또 하나의 장점이라고 생각합니다.
위의 본인이 작성한 일급 컬렉션인 Cars 를 보면 해당 클래스를 통한 객체 생성시 initCar() 를 호출함으로써 상태(field)에 값을 할당하고 난 후에는 컬렉션의 값을 변경할 수 있는 메소드가 없도록 하여 불변 컬렉션이 되도록 하였습니다.
위의 Cars 클래스도 보면 List<Car> 에 접근이 불가하기 때문에 해당 리스트의 값을 변경하거나 추가할 수 없습니다.
여기서 final 선언하면 되지 않느냐고 질문할 수 있는데, 이는 앞서 언급한 일급 컬렉션의 소개와 써야할 이유 해당 블로그에서 찾아볼 수 있듯이 단순히 List 필드에 final을 선언하는 것과는 차이가 큽니다!

아래는 final과의 차이를 이해하기 위한 예시 코드입니다!!

//아래의 코드는 정상적으로 동작합니다.
public void testFinal() {
	private final List<Car> cars = new ArrayList<>();
    
    cars.add(new Car("a"));
    cars.add(new Car("b"));
}
//아래의 코드는 제대로 동작하지 않습니다.
public void testFinal() {
	private final List<Car> cars = new ArrayList<>();
    
    cars = new ArrayList<>();
}

이렇게 불변 컬렉션을 만들어 줌으로써 다음과 같은 이점을 얻을 수 있습니다.

각각의 객체들이 절대 값이 바뀔일이 없다는게 보장되면 그만큼 코드를 이해하고 수정하는데 사이드 이펙트가 최소화된다.
출처: 일급 컬렉션의 소개와 써야할 이유


이상으로 일급 컬렉션에 대한 글을 마무리하도록 하겠습니다. 이번 미션을 통해서는 이름이 있는 컬렉션 에 대한 장점은 느끼지 못하였는데, 향후에 해당 내용에 대한 이점을 스스로 느끼는 순간이 오면 한 번 더 정리해보도록 하겠습니다.😄

좋은 웹페이지 즐겨찾기