안녕하세요, 지난번 자바의 가비지 컬렉션 발표에 이어서 또 다시 기술 세미나를 맡아 시작하게 되었습니다.
특별히 지난번 가비지 컬렉션 발표에 이어서 또 다른 컬렉션에 대해 발표를 해보면 어떨까? 하는 생각에 (농담입니다)
객체지향에서 크루분들이 어려워하는 개념 중 하나인 일급 컬렉션
에 대해 발표를 진행하기로 하였답니다.
1. 일급 컬렉션(first-class-collection)이란?
일급 컬렉션
에 대한 개념은 마틴 파울러
의 책 소트웍스 앤솔러지
에서 처음으로 제시되었습니다.
규칙 8: 일급 콜렉션 사용
이 규칙의 적용은 간단하다. 콜렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다. 각 콜렉션은 그 자체로 포장이 되어있으므로 이제 콜렉션과 관련된 동작은 근거지가 마련된 셈이다. 필터가 이 새 클래스의 일부가 됨을 알 수 있다. 필터는 또한 스스로 함수 오브젝트가 될 수 있다. 또한 새 클래스는 두 그룹을 같이 묶는다든가 그룹의 각 원소에 규칙을 적용하는 등의 동작을 처리할 수 있다. 이는 인스턴스 변수에 대한 규칙의 확실한 확장이지만 그 자체를 위해서도 중요하다. 콜렉션은 실로 매우 유용한 원시 타입이다. 많은 동작이 있지만 후임 프로그래머나 유지보수 담당자에게 의미적 의도나 단초는 거의 없다.
위 내용을 정리해보자면, 일급 컬렉션
이란.
- 컬렉션만을 멤버 변수로 갖는 클래스
- 컬렉션과 관련된 로직을 클래스 내에 캡슐화
를 뜻한다고 할 수 있겠습니다.
예시를 들어보겠습니다.
class Product {
private String name;
private String category;
// 생성자, getter, setter..
}
이러한 상품 객체가 있고
List<Product> products = new ArrayList<>();
products.add(new Product("potato", "vegetables"));
products.add(new Product("robot", "toy"));
이러한 상품의 리스트 컬렉션이 있다고 가정을 해봅시다.
이를 아래와 같이 감싸는 것을 일급 컬렉션
으로 만들었다고 합니다.
public class ProductCollection {
private final List<Product> products;
public ProductCollection(List<Product> products) {
this.products = new ArrayList<>(products);
}
}
이렇게 일급 컬렉션
을 알아보았는데, 이 일급 컬렉션
을 사용하면 어떠한 이점이 있는걸까요?
왜 일급 컬렉션
을 사용해야 한다고 할까요?
2. 왜 일급 컬렉션인가?
2-1. 캡슐화와 관심사의 분리
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())
);
}
}
일급 컬렉션
을 사용하면 컬렉션에 대한 모든 조작과 로직을 해당 컬렉션 클래스 내부에 구현할 수 있습니다.
이로써 로직이 분산되는 것을 방지하고, 관련 로직을 한 곳에서 관리할 수 있습니다.
또 클래스가 단 하나의 책임만을 가지게 됩니다. 이 클래스에서는 products
컬렉션을 관리하는 책임만을 가짐으로써 코드의 가독성과 유지보수성이 향상됩니다.
2-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);
}
}
일급 컬렉션
을 사용하면 불변성 보장을 더욱 용이하게 할 수 있어 외부에서의 컬렉션 상태 변경을 방지할 수 있습니다. 이를 통해 데이터의 안정성과 예측 가능성이 증가합니다. 또한 불변성이 보장되면 사이드 이펙트의 가능성이 줄어들며, 멀티 스레드 환경에서의 안정성이 향상됩니다.
이 코드에서 TeamMembers
클래스는 어떠한 팀의 멤버들의 리스트를 관리합니다. 여기서 멤버를 추가하거나 제거하는 메서드가 클래스 내부에 구현되어 있고, 일반적인 List.add()
와 같은 메서드를 통해 상태변경이 불가능합니다.
즉 이 클래스의 메서드를 통해서만 상태변경이 이루어질 수 있습니다.
getMemebers()
메서드는 불변 리스트를 반환하여 외부에서 컬렉션을 변경하는 것을 방지합니다.
이처럼 일급 컬렉션
을 사용하면 컬렉션의 불변성을 내부 로직을 통해 더욱 강력하게 관리할 수 있습니다.
2-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 IllegalArguemntException("학생 중 18세 이상만 팀에 들어올 수 있습니다");
}
}
}
일급 컬렉션
을 통해서 컬렉션에 적용되어야 할 비즈니스 규칙을 클래스 내부에 정의함으로써 일관된 규칙 적용이 가능합니다.
컬렉션에 추가되거나 변경되는 요소들에 대한 검증 로직을 일급 컬렉션
내부에 구현함으로써, 데이터의 정합성을 보다 체계적으로 관리할 수 있습니다.
addStudent
메서드에서 validateStudent
를 호출하여 학생의 나이가 18세 이상인지 검증합니다. 이렇게 하면 모든 학생 객체가 일관된 규칙을 따를 수 있겠죠?!
3. 일급 컬렉션을 사용할 때의 주의사항
오버엔지니어링의 위험
일급 컬렉션
을 사용할 때 주의해야 할 중요한 점 중 하나는 오버엔지니어링의 위험 입니다. 모든 컬렉션을 일급 컬렉션으로 만드는것이 항상 좋은 것은 아닙니다.
예시를 들어보겠습니다.
public class SimpleDataCollection {
private final List<Data> dataList;
public SimpleDataCollection(List<Data> dataList) {
this.dataList = dataList;
}
}
이 예시에서 SimpleDataCollection
은 복잡한 로직 없이 데이터만을 저장합니다. 이런 경우 일급 컬렉션
을 사용하는 것은 오버엔지니어링이 될 수 있습니다.
이처럼 일급 컬렉션
은 컬렉션에 복잡한 비즈니스 로직이나 규칙이 적용될 때 가장 유용합니다. 단순히 데이터를 저장하는 용도라면 굳이 일급 컬렉션
을 사용할 필요는 없겠죠.
따라서 일급 컬렉션
을 도입하기 전에 해당 컬렉션에 어떤 로직이나 규칙이 필요한지 고려해야 합니다. 컬렉션의 복잡성과 유지보수의 편의성을 균형있게 고려하는 것이 중요합니다.
4. 결론
일급 컬렉션
을 오늘은 함께 알아보았는데요, 우리는 일급 컬렉션이 단순한 데이터의 모음 이상의 가치를 지닌다는 것을 배웠습니다.
이는 객체지향 프로그래밍의 근본적인 원칙인 캡슐화와 응집도를 강화하는데 큰 도움을 줍니다.
비즈니스 로직을 한 곳에 집중함으로써, 우리의 코드는 더욱 명확하고, 유지보수하기 쉬우며, 오류 발생가능성을 줄일 수 있습니다.
하지만 모든 상황에서 일급 컬렉션
이 해답은 아닙니다. 우리는 오버엔지니어링의 위험성을 항상 염두하고 일급 컬렉션이 꼭 필요한 상황인지를 항상 고려해야 합니다.
오늘 공유한 내용이 실제 프로젝트에서 여러분이 마주칠 문제를 해결하는데 도움이 되길 바랍니다.