First Class Collection

일급 컬렉션(First Class Collection)은 언어가 제공하는 원시 컬렉션(List, Map, Set 등)을 한 번 래핑하고, 그 외의 다른 멤버 변수를 가지지 않는 클래스를 말한다. 이 클래스는 내부 컬렉션을 조작하고 검증하는 모든 비즈니스 규칙과 로직을 독점적으로 관리하며, 객체지향 프로그래밍과 도메인 주도 설계(DDD)에서 원시 컬렉션을 다루는 로직이 서비스 계층에 흩어지는 문제를 막기 위한 설계 패턴이다.

왜 필요한가: Service에 흩어지는 컬렉션 로직

// 문제 상태: Service에 컬렉션 로직이 분산
public class RacingService {
    public List<Car> getWinners(List<Car> cars) {
        int max = cars.streamgetPosition).max().orElse(0;
        return cars.stream().filter(c -> c.getPosition() == max).toList();
    }
}
// 해결: 일급 컬렉션 Cars 도입으로 로직 캡슐화
public class Cars {
    private final List<Car> cars;

    public Cars(String[] names) {
        this.cars = Arrays.stream(names)
                          .maptrim
                          .mapnew
                          .toList();
    }

    public List<Car> getWinners() {
        int max = cars.streamgetPosition).max().orElse(0;
        return cars.stream().filter(c -> c.getPosition() == max).toList();
    }
}

구조 규칙

일급 컬렉션을 만드는 절차는 다음 순서로 정리할 수 있다.

  1. 원시 컬렉션(List, Set 등)을 유일한 인스턴스 변수로 가지는 클래스를 만든다.
  2. 생성자에서 컬렉션 요소의 유효성을 검증한다(크기, 중복, 특정 상태 조건 등).
  3. 외부로 컬렉션을 노출하지 않거나, 노출해야 할 경우 불변 컬렉션으로 반환해 외부의 임의 조작을 막는다.
  4. 컬렉션 요소를 다루는 비즈니스 로직(필터링, 상태 업데이트 등)을 일급 컬렉션의 메서드로 구현해, 외부(Service)로 로직이 새어나가지 않게 한다.

이 구조는 "원시값을 포장(wrapping primitives)한다"는 원칙을 컬렉션으로 확장한 것이라고 볼 수 있다. 원시 컬렉션 자체는 도메인의 의미나 제약 조건을 표현할 수 없기 때문에, 이를 다루는 쪽(주로 Service나 Controller)이 검증·로직을 떠안는 논리적 누수가 생긴다는 것이 이 페이지의 해석이다.[3]

이웃 개념과의 경계

절충점과 실패 모드

관련

출처

테스트 질문


  1. 일급 컬렉션 도입 — Cars.md — "여러 대의 자동차(Car) 객체를 관리하는 로직과 상태 변이가 Service 클래스 이곳저곳에 중복 생성되며 코드가 복잡해짐.", List<Car>를 포장하는 Cars 일급 컬렉션으로 컬렉션 관련 로직을 이동시키는 리팩토링 예시. ↩︎ ↩︎

  2. 객체지향 설계 실전 — Cars에 우승자 판별·최대 위치 계산을 모으고 외부의 내부 컬렉션 직접 접근을 차단하는 예시를 제시한다. ↩︎

  3. 출처 매핑 미확인 — DTO와의 차이, 일반 래퍼 클래스와의 차이, "원시값 포장 원칙의 확장"이라는 해석은 이 페이지가 인용하는 소스(Cars 리팩토링 노트)에는 등장하지 않는다. 객체지향 설계·DDD 일반 원칙에서 널리 통용되는 구분이지만, 이 볼트 안에서 별도로 grep 가능한 출처는 아직 없다. ↩︎ ↩︎ ↩︎