본문 바로가기
Java/Java Story

[Java] First Class Collection(일급 컬렉션)

by 잭피 2020. 12. 6.

 

 

 

 

안녕하세요~ 잭코딩입니다!

 

이번에는 일급 컬렉션에 대해 글을 써보려고 합니다!

요즘 글쓴이는 우아한테크캠프 Pro라는 교육과정을 듣고 있습니다

이번 미션에서 일급 컬렉션을 적용해보라는 코드리뷰를 받아서 관련 내용을 학습할 수 있었습니다

일급 컬렉션을 쓰면 무엇이 좋은지 살펴볼까요?


First Class Collection(일급 컬렉션) ?

간단히 설명하면 Collection들을 한번 Wrapping한 컬렉션입니다

예를 들어 Car라는 객체가 있습니다

public class Car {

    private int position;

    public void move(MovingStrategy movingStrategy) {
        if (movingStrategy.isMove()) {
            position++;
        }
    }

    public int getPosition() {
        return position;
    }
}

Car 객체의 인스턴스 3개를 생성합니다

Car aCar = new Car() // Car 인스턴스 1
Car bCar = new Car() // Car 인스턴스 2
Car cCar = new Car() // Car 인스턴스 3

이 인스턴스들을 모두 함께 관리해야할 때, 일급 컬렉션인 Cars 클래스를 만들면 좋습니다

Car 인스턴스들의 묶음 역할을 해주는 Cars를 정의합니다

public class Cars {

    private List<Car> cars;

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

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

이렇게 일급 컬렉션을 만들면 무엇이 좋을까요?

 

사실 처음 코드 리뷰를 받았을 땐, 그냥 Veiw에서 for문을 통해 출력하면 되지 않나?
이렇게 객체를 하나 더 정의하는게 불편하지 않을까? 라는 생각을 했습니다 
하지만 코드를 직접 고쳐보면서 학습을 해보니 일급 컬렉션의 장점을 확실히 알 수 있었습니다
그렇다면 장점을 한번 살펴볼까요?

First Class Collection(일급 컬렉션)의 장점

그렇다면 한번에 관리할 수 있는 이점말고 또 다른 이점을 살펴볼까요?

일급 컬렉션의 장점의 경우 리뷰어님이 공유해주신 티스토리통해 학습하였습니다

참고한 블로그에서는 일급 컬렉션의 장점이 불변성을 보장한다는 내용이 있었습니다

그 부분에 대해서는 조금 다른 생각을 가지고 있어서 글쓴이가 생각한 내용을 바탕으로 적었습니다

(혹시나 다른 생각을 가지고 있으시다면 댓글 남겨주시면 감사하겠습니다)

 

그러면 하나씩 살펴볼까요?

1. 하나의 인스턴스에서 비지니스 로직을 관리할 수 있습니다

예를 들어 자동차의 현재 위치가 3보다 큰 자동차를 가져오려고 합니다

일급 컬렉션 Cars가 없다면 아래처럼 하나씩 가져오거나, List로 묶어서 처리해야합니다

@Test
public void _3보다_큰_자동차가져오기() {
    Car aCar = new Car(); // Car 인스턴스 1
    Car bCar = new Car(); // Car 인스턴스 2
    Car cCar = new Car(); // Car 인스턴스 3

    List<Car> cars = new ArrayList<>();
    cars.add(aCar);
    cars.add(bCar);
    cars.add(cCar);

    cars.stream()
            .filter(car -> car.getPosition() > 3)
            .forEach(System.out::println);
}

이렇게 List를 만들고 각각의 Car 인스턴스를 추가하고 가져온다면 중간에 빼먹는 실수를 할 수도 있고 비지니스 로직도 외부에서 관리하게 됩니다

 

일급 컬렉션 Cars를 이용해볼까요?

public class Cars {

    private List<Car> cars;

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

    public List<Car> getCars() {
        return cars;
    }
    
    public List<Car> getCarsOverPosition(int position) {
        return cars.stream()
                .filter(car -> car.getPosition() > position)
                .collect(Collectors.toList());
    }
}

처음에 Car객체를 생성해서 바로 불변 클래스 Cars로 관리한다면 더이상 Car가 추가되거나 삭제되지 않으니 빼먹는 실수도 없습니다

 

또한 비지니스 로직도 따로 서비스로 빼서 분리하지 않고 getCarsOverPosition(int position)이라는 메소드를 정의해서 Cars 도메인에서 관리할 수 있습니다

 

2. 리스트 내의 객체의 상태를 동일하게 관리할 수 있습니다

이 말은 일급 컬렉션이 불변성을 보장한다는 뜻이 아닙니다 

일급 컬렉션은 불변성을 보장하지 않으며, 보장하도록 구현해야 할 필요도 없습니다

 

 

소트웍스 엔솔러지 책에서 Rule을 보더라도 일급 컬렉션은 불변으로 만들어야 한다는 말은 없습니다

 

글쓴이가 생각하는 일급 컬렉션의 이점 아래와 같습니다

"같은 비지니스 로직에 따라 함께 관리할 수 있다" 

 

그러면 잠깐 객체의 불변성에 대해 이야기해보겠습니다

우선 Car 객체는 스스로 move()할 수 있습니다

Car 객체를 불변으로 만들고 싶다면 final로 사용하면 되지 않을까요?

NO~!

public class FirstCollectionTest {

    MovingStrategy movingStrategy;
    RandomGenerator randomGenerator;

    @BeforeEach
    void setUp() {
        randomGenerator = mock(RandomGenerator.class);
        movingStrategy = new RandomMovingStrategy(randomGenerator);
    }

    @Test
    @DisplayName("final 인스턴스 상태값 변경 테스트")
    public void final_Change_Value() {
        final Car car = new Car();
        when(randomGenerator.generate()).thenReturn(7);

        // 자동차의 상태(position) 1이 증가한다
        car.move(movingStrategy);
        assertEquals(car.getPosition(), 1);

        // 자동차의 상태(position) 1이 증가한여 2가된다
        car.move(movingStrategy);
        assertEquals(car.getPosition(), 2);
    }
}

final로 선언했지만 자동차가 이동할 때마다 상태값이 변합니다

Java에서 final은 객체를 불변으로 만들어주는게 아니라 재할당만 금지시켜줍니다

 

    @Test
    @DisplayName("fianl 재할당 금지")
    public void final_Reassignment_Reprohibition() {
        final Car car = new Car();
        car = new Car();
    }

 

위 코드처럼 final 인스턴스를 재할당해주려고 하면 컴파일 에러가 발생합니다

이 이외에도 인스턴스 내 setter가 있다면 final 인스턴스는 값 변경이 가능합니다 

 

이런 문제를 Java에서는 일급 컬렉션으로 해결할 수 있습니다

Car를 List로 묶어서 Cars 일급 컬렉션으로 생성합니다

위에서 정의했던 일급 컬렉션 Cars를 다시 볼까요?

public class Cars {

    private List<Car> cars;

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

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

리스트 내에 있는 Car 인스턴스들 각각을 변경할 수 없습니다 

List라는 컬렉션에 접근할 수 있는 방법이 없기 때문에 값을 변경하거나 추가할 수 없습니다

 

일급 컬렉션은 불변 클래스가 아니어도 됩니다

모든 자동차에 필요한 비지니스 로직을 적용해야하면 cars의 상태를 변경해도 괜찮습니다

따라서 모든 자동차의 상태를 변경해야하는 비지니스 로직이 필요하다면 아래처럼 추가할 수 있습니다

public void moveCars(MovingStrategy movingStrategy) {
    cars.stream()
            .forEach(car -> car.move(movingStrategy));
}
이렇게 일급 컬렉션을 학습하여 적용하고 블로그 정리까지 해봤네요
혹시나 추가적인 내용이 있거나 제가 잘못 생각한 내용이 있다면 댓글에 남겨주시면 감사하겠습니다

 

댓글