
일급 컬렉션(First-Class Collection)은 단순한 데이터 컬렉션을 하나의 객체로 묶어,
해당 컬렉션과 관련된 모든 로직·검증·행위를 한 클래스에서 관리하도록 돕는 설계 방식입니다.
이 글을 통해 여러분들은 다음 내용을 명확하게 이해할 수 있을거에요
- 일급 컬렉션의 개념
- 일급 컬렉션을 사용하는 이유
- 적용하기 좋은 상황
- 실제 코드 구현 방법
- 오버엔지니어링을 피하기 위한 기준
일급 컬렉션(first-class-collection)이란?

일급 컬렉션(First-Class Collection)은 마틴 파울러(Martin Fowler)가 발매한 책 소트웍스 앤솔러지(SortWorks Anthology)에서 소개한 규칙 중 하나입니다.
규칙8: 일급 콜렉션 사용
이 규칙의 적용은 간단하다. 콜렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다. 각 콜렉션은 그 자체로 포장이 되어있으므로 이제 콜렉션과 관련된 동작은 근거지가 마련된 셈이다. 필터가 이 새 클래스의 일부가 됨을 알 수 있다. 필터는 또한 스스로 함수 오브젝트가 될 수 있다. 또한 새 클래스는 두 그룹을 같이 묶는다든가 그룹의 각 원소에 규칙을 적용하는 등의 동작을 처리할 수 있다. 이는 인스턴스 변수에 대한 규칙의 확실한 확장이지만 그 자체를 위해서도 중요하다. 콜렉션은 실로 매우 유용한 원시 타입이다. 많은 동작이 있지만 후임 프로그래머나 유지보수 담당자에게 의미적 의도나 단초는 거의 없다.
핵심 개념을 정리해보면 아주 단순합니다.
- 컬렉션만을 멤버 변수로 갖는 클래스
클래스 내부에는 List, Set, Map 등 하나의 컬렉션만 존재합니다. - 컬렉션과 관련된 모든 로직을 내부에 캡슐화
필터링, 정렬, 검증, 변환 같은 작업을 외부에서 흩어 처리하지 않고
일급 컬렉션이 직접 책임지고 수행합니다.
왜 일급 컬렉션을 사용하는가?
일급 컬렉션은 단순히 코드를 깔끔하게 만드는 수준을 넘어,
로직의 캡슐화, 불변성 유지, 비즈니스 규칙의 중앙화 등 구조적 이점을 제공합니다.
1. 로직의 캡슐화와 관심사 분리
일반적으로 컬렉션 관련 로직은 프로젝트 곳곳에 흩어지기 쉽습니다.
예를 들어 List<Product> 기반으로 필터링하거나 정렬할 때마다 반복적으로 stream, filter, sort 같은 코드가 등장합니다.
일급 컬렉션은 이 로직들을 한 클래스에 모아 문제를 해결합니다.
public class ProductionCollection {
private final List<product> products;
public ProductCollection(List<Product> products) {
this.products = new ArrayList<>(products);
}
public ProductCollection filterByCategory(String category) {
return new ProductCollection(
products.stream()
.filter(product -> product.getCategory().equals(category))
.collect(Collectors.toList())
);
}
}
이제 “카테고리로 필터하기”라는 동작은 외부에 흩어진 로직이 아니라ProductCollection이 책임지는 명확한 행위가 됩니다.
얻을 수 있는 효과
- 관련 로직이 한 곳에 모여 유지보수 용이
- 컬렉션 객체가 단일 책임을 가져 응집도 증가
- 도메인 로직이 명확하게 드러나 가독성 향상
2. 불변성 유지가 쉬움
일급 컬렉션은 컬렉션의 불변성을 확보하는 데에도 도움이 됩니다.
외부에서 직접 컬렉션을 수정하지 못하도록 막고, 모든 변경을 클래스 내부 로직을 통해 수행하도록 강제할 수 있습니다.
public class TeamMembers {
private final List<Member> members;
public TeamMembers(List<Member> members) {
this.members = new ArrayList<>(members);
}
public void addMember(Member member) {
members.add(member);
}
public void removeMember(Member member) {
members.remove(member);
}
public List<Member> getMembers() {
return Collections.unmodifiableList(members);
}
}
이 구조의 장점
- 외부에서
members.add()같은 직접 조작 불가능 - 모든 상태 변경은 반드시
TeamMembers의 메서드를 통해 발생 - 예측 가능한 코드 작성 및 사이드 이펙트 감소
- 멀티스레드 환경에서 더 안전
3. 비즈니스 규칙의 중앙화
컬렉션에 요소를 추가할 때는 나이 제한, 중복 체크, 최대 개수 등 다양한 규칙이 필요한 경우가 많습니다.
일급 컬렉션은 이러한 규칙을 한 곳에서 관리하도록 도와줍니다.
public class StudentCollection {
private final List<Student> students;
public StudentCollection(List<Student> students) {
this.students = new ArrayList<>();
for (Student student : students) {
addStudent(student);
}
}
public void addStudent(Student student) {
validateStudent(student);
students.add(student);
}
private void validateStudent(Student student) {
if (student.getAge() < 18) {
throw new IllegalArgumentException("학생은 18세 이상만 허용됩니다.");
}
}
}
StudentCollection은 학생 리스트 관리라는 도메인 규칙을 명확히 포함하는 객체가 됩니다.
장점
- 모든 요소가 동일한 규칙을 통해 검증됨
- 컬렉션 상태의 정합성을 클래스 내부에서 완전히 보장
- 비즈니스 의도가 코드에 그대로 드러남
일급 컬렉션 예시
일급 컬렉션은 “컬렉션을 객체로 승격한다”는 개념을 실제 코드로 보면 훨씬 명확해집니다.
아래 예시는 가장 기본적인 형태 → 로직 포함 → 검증 포함 순서로 구성되어 있어 단계적으로 이해할 수 있습니다.
일급 컬렉션의 기본적인 형태
가장 단순한 형태의 일급 컬렉션은 “컬렉션 하나를 멤버 변수로 보유하는 클래스”입니다.
public class ProductCollection {
private final List<Product> products;
public ProductCollection(List<Product> products) {
this.products = new ArrayList<>(products);
}
}
여기서는 특별한 로직이 없지만,
- 외부에서 컬렉션을 직접 수정하지 못하도록 방지하고
- 이후 필터링·정렬·검증 같은 기능을 이 클래스 내부에 추가할 수 있는 기반
을 마련합니다.
즉, 도메인 컬렉션을 하나의 객체로 다루기 위한 출발점이 됩니다.
로직 포함하는 일급 컬렉션
일급 컬렉션의 가장 큰 장점은 컬렉션과 관련된 행위(로직)를 내부에 담을 수 있다는 점입니다.
예를 들어, 특정 카테고리의 상품만 필터링하는 기능을 외부가 아닌 일급 컬렉션이 수행하도록 만들 수 있습니다.
public ProductCollection filterByCategory(String category) {
return new ProductCollection(
products.stream()
.filter(product -> product.getCategory().equals(category))
.collect(Collectors.toList())
);
}
이렇게 구현하면
- 필터링 로직이 분산되지 않고 한 곳에 모여 유지보수가 쉬워짐
ProductCollection이라는 이름만 봐도 "상품 컬렉션을 다루는 행위"를 쉽게 추론할 수 있음- 도메인 로직이 클래스 내부에 자연스럽게 응집됨
결과적으로 컬렉션을 사용하는 코드가 아니라, 컬렉션 객체가 가진 책임이라는 의미가 더 명확해집니다.
검증 로직을 포함한 일급 컬렉션
일급 컬렉션은 요소를 추가하거나 수정할 때 필요한 비즈니스 규칙을 강제하는 역할도 맡을 수 있습니다.
예를 들어, 학생 컬렉션에서 “18세 미만은 추가할 수 없다”는 규칙을 중앙에서 관리할 수 있습니다.
private void validateStudent(Student student) {
if (student.getAge() < 18) {
throw new IllegalArgumentException("학생은 18세 이상만 허용됩니다.");
}
}
이 검증 메서드를 addStudent()와 같은 메서드 안에서 호출하면
- 모든 요소가 동일한 규칙을 통과하도록 강제됨
- 규칙이 여러 곳에 중복되지 않아 변경 시 유지보수 비용이 감소
- 도메인 정책이 코드에 명확하게 드러나 가독성이 향상
즉, 일급 컬렉션이 도메인 규칙을 지켜주는 단일 책임(관문) 역할을 하게 됩니다.
일급 컬렉션 적용 시 주의사항
일급 컬렉션은 강력한 설계 방식이지만, 모든 컬렉션에 무조건 적용해야 하는 규칙은 아닙니다.
상황을 잘못 판단하면 오히려 클래스만 늘어나 복잡도가 증가할 수 있습니다.
오버엔지니어링의 위험
컬렉션을 특별히 가공하거나 검증할 필요가 없고, 단순히 데이터를 보관하기만 한다면
일급 컬렉션을 도입하는 것은 지나친 설계가 될 수 있습니다.
public class SimpleDataCollection {
private final List<Data> dataList;
public SimpleDataCollection(List<Data> dataList) {
this.dataList = dataList;
}
}
이처럼 로직 없이 단순히 감싸기만 한 클래스는
실제로는 아무런 이점을 제공하지 않으며, 유지보수해야 할 코드만 늘어납니다.
언제 사용해야할까?
아래와 같은 상황에서는 일급 컬렉션의 장점이 확실히 드러납니다.
- 컬렉션에 명확한 비즈니스 규칙이 있는 경우
- 필터링·검증·정렬 같은 로직이 프로젝트 여러 곳에서 반복되는 경우
- 컬렉션의 불변성을 유지해야 하는 경우
- 컬렉션과 관련된 책임을 한 곳으로 모아 관리하고 싶은 경우
요약하자면, 컬렉션이 단순한 데이터가 아니라 도메인의 의미를 가질 때
일급 컬렉션을 도입하면 설계 품질을 확실히 높일 수 있습니다.
일급 컬렉션은 컬렉션을 단순한 데이터 리스트가 아닌,
도메인 안에서 의미를 가진 하나의 객체로 다루도록 도와주는 설계 방식입니다.
상황에 맞게 적용하면 로직이 흩어지는 것을 막고,
더 명확하고 유지보수하기 쉬운 도메인 모델을 만들 수 있습니다.
프로젝트 규모가 커질수록 이런 작은 구조적 선택이 큰 차이를 만들어냅니다.
필요한 곳에만 적절하게 활용해보세요 :)
'Computer Science > 프로그래밍 이론 💬' 카테고리의 다른 글
| 비동기 프로그래밍 이해하기 - 동기 방식과 뭐가 다를까? (0) | 2024.02.06 |
|---|---|
| Dependency Injection이란? (0) | 2024.02.06 |
| 제어의 역전(IOC) 톺아 보기 (0) | 2024.02.06 |
| 객체지향 프로그래밍이란? (0) | 2024.02.02 |
안녕하세요, 저는 주니어 개발자 박석희 입니다. 언제든 하단 연락처로 연락주세요 😆