개요
자바에서 "일급 컬렉션(First-Class Collection)"이라는 용어는 컬렉션을 래핑하고, 그 외의 다른 멤버 변수가 없는 단일 클래스를 의미합니다. 이 개념은 객체지향 프로그래밍에서 컬렉션을 좀 더 안전하고, 유지보수성이 높으며, 응집도가 높은 방식으로 관리하기 위해 사용됩니다.
1. 목적
일급 컬렉션의 주된 목적은 비즈니스 로직에서 사용되는 컬렉션에 대해 더 풍부한 행동을 제공하고, 컬렉션의 불변성을 유지하며, 보다 명시적인 이름을 통해 컬렉션의 의도를 분명히 하는 것입니다. 이는 컬렉션의 사용을 보다 안전하고 의미 있게 만들어, 코드의 가독성과 유지보수성을 향상시킵니다.
2. 필요한 이유
1. 컬렉션에 대한 연산 로직이 여러 곳에 분산되는 것을 방지
- 상세 설명: 일반적으로 컬렉션과 관련된 연산 로직(필터링, 정렬 등)은 비즈니스 로직이 위치하는 여러 클래스에 흩어져 있기 쉽습니다. 이로 인해 유사한 로직이 중복되거나, 컬렉션에 대한 변경 사항이 발생했을 때 여러 클래스를 수정해야 하는 상황이 발생할 수 있습니다. 일급 컬렉션을 사용하면 이러한 연산 로직을 컬렉션을 감싼 단일 클래스 내부로 캡슐화하여 관리할 수 있으며, 이는 로직의 중복을 줄이고 코드의 일관성을 유지하는 데 도움이 됩니다.
2. 컬렉션의 불변성을 유지하기 위해
- 상세 설명: 컬렉션의 불변성은 컬렉션의 상태가 생성된 이후 변하지 않음을 보장하는 것입니다. 컬렉션이 불변하면, 버그 발생 가능성이 줄고, 멀티스레드 환경에서의 안정성이 향상됩니다. 일급 컬렉션을 사용하면 컬렉션에 대한 수정을 허용하는 메서드를 제어할 수 있으며, 외부에서 직접 컬렉션을 변경할 수 없게 함으로써 불변성을 쉽게 유지할 수 있습니다.
3. 비즈니스 규칙을 컬렉션 수준에서 강제할 수 있습니다
- 상세 설명: 일급 컬렉션을 사용하면 컬렉션에 적용되어야 하는 비즈니스 규칙을 클래스 내부에 명시적으로 정의할 수 있습니다. 예를 들어, 특정 요소가 컬렉션에 추가될 때 유효성 검사를 수행하거나, 컬렉션에서 요소를 제거하는 것을 제한할 수 있습니다. 이는 비즈니스 로직의 일관성과 정확성을 보장하는 데 중요한 역할을 합니다.
4. 컬렉션에 대한 재사용성과 테스트 용이성을 높일 수 있습니다
- 상세 설명: 일급 컬렉션을 사용하면 특정 비즈니스 로직을 수행하는 메서드들을 한 곳에 모을 수 있습니다. 이로 인해 해당 컬렉션과 관련된 기능을 다른 부분의 코드에서도 재사용하기 쉬워집니다. 또한, 일급 컬렉션 내부의 메서드들은 독립적인 단위로 테스트하기가 용이해집니다. 테스트 코드에서 일급 컬렉션의 인스턴스를 생성하고, 예상되는 동작이나 결과를 쉽게 검증할 수 있습니다. 이는 전체적인 코드 베이스의 테스트 커버리지를 향상시키고, 안정성을 보장하는 데 도움이 됩니다.
3. 장점 및 예시 코드
장점: 비즈니스 로직 캡슐화, 불변성 유지, 재사용성 및 테스트 용이성 증가
예시 코드:
public class Employees {
private final List<Employee> employees;
public Employees(List<Employee> employees) {
this.employees = new ArrayList<>(employees);
}
public void add(Employee employee) {
employees.add(employee);
}
public List<Employee> getSortedByName() {
return employees.stream()
.sorted(Comparator.comparing(Employee::getName))
.collect(Collectors.toList());
}
}
위 코드에서 Employees
클래스는 List<Employee>
를 래핑하고, 이름으로 정렬된 직원 목록을 가져오는 등의 추가적인 비즈니스 로직을 제공합니다.
4. 단점 및 예시 코드
단점: 때때로 과도한 래핑으로 인해 코드의 복잡성이 증가할 수 있습니다.
예시 코드:
public class EmployeeOperations {
private Employees employees;
public EmployeeOperations(Employees employees) {
this.employees = employees;
}
// 여기서 Employees를 직접 사용하지 않고 한 번 더 래핑하여 복잡성 증가
public void performSomeOperation() {
// 복잡한 비즈니스 로직
}
}
5. 단점을 극복할 방법 및 예시 코드
단점을 극복하는 방법 중 하나는 일급 컬렉션을 사용할 때, 그 이점이 명확히 드러나는 상황에서만 사용하는 것입니다. 또한, 일급 컬렉션 내부에서만 사용되는 메서드는 private으로 선언하여 외부에 불필요한 API 노출을 최소화할 수 있습니다.
예시 코드:
public class Employees {
private final List<Employee> employees;
public Employees(List<Employee> employees) {
this.employees = new ArrayList<>(employees);
}
// 필요한 경우에만 공개 메서드 제공
public List<Employee> filterByDepartment(String department) {
return employees.stream()
.filter(e -> e.getDepartment().equals(department))
.collect(Collectors.toList());
}
// 내부에서만 사용하는 메서드는 private으로 선언
private boolean isValid(Employee employee) {
// 검증 로직
return true;
}
}
위의 접근 방식은 일급 컬렉션의 사용을 보다 의미 있게 만들고, 불필요한 복잡성을 피하는 데 도움이 됩니다.
일급 컬렉션 사용(X) -> 사용(O) 코드
아래는 일급 컬렉션을 사용하지 않은 코드와 이를 일급 컬렉션으로 리팩토링한 코드의 예시들입니다. 각각의 예시는 다양한 상황에서 일급 컬렉션의 적용 방법을 보여줍니다.
예시 1: 사용자 목록 관리
사용하지 않은 경우:
List<User> users = new ArrayList<>();
users.add(new User("Alice"));
users.add(new User("Bob"));
// 사용자 목록에 대한 작업...
리팩토링 후:
class Users {
private final List<User> users;
Users(List<User> users) {
this.users = users;
}
void add(User user) {
users.add(user);
}
List<User> getUsers() {
return users;
}
}
호출 코드
List<User> userList = new ArrayList<>();
userList.add(new User("Alice"));
userList.add(new User("Bob"));
Users users = new Users(userList);
users.add(new User("Charlie"));
List<User> retrievedUsers = users.getUsers();
예시 2: 주문 항목 관리
사용하지 않은 경우:
List<OrderItem> orderItems = new ArrayList<>();
orderItems.add(new OrderItem("Laptop", 1, 1000.0));
orderItems.add(new OrderItem("Mouse", 2, 40.0));
// 주문 항목 목록에 대한 작업...
리팩토링 후:
class OrderItems {
private final List<OrderItem> orderItems;
OrderItems(List<OrderItem> orderItems) {
this.orderItems = orderItems;
}
void add(OrderItem orderItem) {
orderItems.add(orderItem);
}
double getTotalPrice() {
return orderItems.stream().mapToDouble(OrderItem::getTotalPrice).sum();
}
}
호출 코드
List<OrderItem> orderItemList = new ArrayList<>();
orderItemList.add(new OrderItem("Laptop", 1, 1000.0));
orderItemList.add(new OrderItem("Mouse", 2, 40.0));
OrderItems orderItems = new OrderItems(orderItemList);
orderItems.add(new OrderItem("Keyboard", 1, 100.0));
double totalPrice = orderItems.getTotalPrice();
예시 3: 도서 목록 관리
사용하지 않은 경우:
List<Book> books = new ArrayList<>();
books.add(new Book("Java Fundamentals", "Programming"));
// 도서 목록에 대한 작업...
리팩토링 후:
class Books {
private final List<Book> books;
Books(List<Book> books) {
this.books = books;
}
void add(Book book) {
books.add(book);
}
List<Book> findByCategory(String category) {
return books.stream().filter(book -> book.getCategory().equals(category)).collect(Collectors.toList());
}
}
호출 코드
List<Book> bookList = new ArrayList<>();
bookList.add(new Book("Java Fundamentals", "Programming"));
Books books = new Books(bookList);
books.add(new Book("Effective Java", "Programming"));
List<Book> programmingBooks = books.findByCategory("Programming");
예시 4: 점수 목록 관리
사용하지 않은 경우:
List<Integer> scores = new ArrayList<>();
scores.add(90);
scores.add(80);
// 점수 목록에 대한 작업...
리팩토링 후:
class Scores {
private final List<Integer> scores;
Scores(List<Integer> scores) {
this.scores = scores;
}
void add(int score) {
scores.add(score);
}
double getAverage() {
return scores.stream().mapToInt(Integer::intValue).average().orElse(0.0);
}
}
호출 코드
List<Integer> scoreList = new ArrayList<>();
scoreList.add(90);
scoreList.add(80);
Scores scores = new Scores(scoreList);
scores.add(85);
double averageScore = scores.getAverage();
예시 5: 상품 목록 관리
사용하지 않은 경우:
List<Product> products = new ArrayList<>();
products.add(new Product("Laptop", 1200.00));
// 상품 목록에 대한 작업...
리팩토링 후:
class Products {
private final List<Product> products;
Products(List<Product> products) {
this.products = products;
}
void add(Product product) {
products.add(product);
}
List<Product> findByPriceGreaterThan(double price) {
return products.stream().filter(product -> product.getPrice() > price).collect(Collectors.toList());
}
}
호출 코드
List<Product> productList = new ArrayList<>();
productList.add(new Product("Laptop", 1200.00));
Products products = new Products(productList);
products.add(new Product("Smartphone", 800.00));
List<Product> expensiveProducts = products.findByPriceGreaterThan(1000.00);
일급 컬렉션을 사용하면 컬렉션과 관련된 비즈니스 로직을 하나의 클래스로 캡슐화할 수 있습니다. 이를 통해 코드의 가독성을 높이고, 관련 로직을 한 곳에서 관리할 수 있게 됩니다. 위의 예시들은 일급 컬렉션을 사용하여 리팩토링한 경우, 코드가 어떻게 개선될 수 있는지 보여줍니다.
'프로그래밍 언어 > 자바' 카테고리의 다른 글
자바의 자원 관리 방법 비교: try-with-resources와 try-finally (0) | 2024.06.06 |
---|---|
ArrayList, LinkedList, Vector 차이 (0) | 2024.04.22 |