Replies: 4 comments
-
일급컬렉션이란저는 일급 컬렉션을 컬렉션을 단순히 감싼 객체가 아니라, 예를 들어 List를 그대로 사용하면 자동차들을 다루는 책임이 보통 외부로 새어나갑니다. for (Car car : cars) {
car.move();
}이런 코드가 Controller나 Service 같은 외부 객체에 있으면, 우승자를 뽑는 로직, 가장 멀리 간 위치를 찾는 로직, 경주가 끝났는지 판단하는 로직도 마찬가지입니다. (그저 맥락 이해를 위한 예시로만 받아들여주세요 !, 적절한 책임에 대해서는 고려 하지 않은 예시 입니다.) 반면 Cars라는 일급 컬렉션을 만들면 이런 식으로 메시지를 보낼 수 있습니다.
외부 객체는 더 이상 List를 직접 순회하면서 처리하지 않아도 됩니다. 이 점에서 일급 컬렉션은 단순히 List를 한 번 포장한 객체가 아니라, 추상화 수준List도 객체인데 왜 굳이 Cars로 감싸야 하냐고 볼 수도 있습니다. List는 자료구조입니다. 반면 Cars는 도메인 객체입니다.
그래서 Cars는 내부에 List를 쓰든, Set을 쓰든, Map을 쓰든 외부가 알 필요 없게 만들 수 있습니다. 외부는 내부 자료구조가 아니라 moveAll(), winners() 같은 도메인 메시지에만 의존하게 됩니다. 장점1. 컬렉션 관련 로직을 한 곳에 모을 수 있다컬렉션을 사용하는 코드에서는 보통 순회, 필터링, 정렬, 최대값 계산 같은 로직이 자주 나옵니다. 이 로직을 외부에 두면 코드가 점점 절차적으로 변하기 쉽습니다. List<Car> winners = cars.stream()
.filter(car -> car.isSamePosition(maxPosition))
.toList();이런 로직이 외부에 있으면 Car 목록의 구조와 우승자 판단 방식이 외부로 새어 나갑니다. public List<Car> winners() {
int maxPosition = findMaxPosition();
return cars.stream()
.filter(car -> car.isSamePosition(maxPosition))
.toList();
}그 결과 자동차들에 대한 로직이 한 곳에 모여 응집도가 올라가고, 2. 컬렉션의 불변식을 보호할 수 있다일급 컬렉션의 또 다른 장점은 컬렉션이 지켜야 하는 규칙을 객체 내부에 둘 수 있다는 점입니다. public Cars(List<Car> cars) {
validateEmpty(cars);
validateDuplicateName(cars);
this.cars = cars;
}이렇게 하면 Cars 객체는 생성되는 순간부터 유효한 상태를 가질 수 있습니다. 내부 컬렉션 노출의 위험성(게터 우회 문제)하지만 여기서 내부 리스트를 그대로 반환하면 문제가 생깁니다. public List<Car> getCars() {
return cars;
}이때 반환된 List는 Cars 내부에 있는 그 리스트와 같은 참조입니다. 그래서 외부에서 아래처럼 조작할 수 있습니다. List<Car> values = cars.getCars();
values.clear();
values.add(new Car("pobi"));그러면 Cars는 자기 상태가 바뀌었는지도 모르는 상태에서 내부 값이 변경됩니다. 이게 문제인 이유는 객체가 자기 상태를 스스로 통제하지 못하게 되기 때문이라고 생각합니다. 생성자에서 중복 이름 검증이나 최소 자동차 개수 검증을 해두었다고 해도, 외부에서 getCars()로 꺼낸 리스트를 수정하면 그 검증을 우회할 수 있습니다. 즉, Cars가 보장해야 하는 불변식이 외부 우회로를 통해 깨질 수 있습니다. 그래서 일급 컬렉션을 만들 때는 단순히 클래스로 감싸는 것에서 끝나지 않고, 3. 메시지 중심의 코드가 된다일급 컬렉션을 사용하면 외부에서 컬렉션을 꺼내서 처리하기보다,
이런 방식은 getCars()로 리스트를 꺼내서 외부에서 처리하는 것보다, 즉, 외부 객체가 내부 상태를 물어보고 직접 판단하는 것이 아니라, 이 점에서 일급 컬렉션은 Tell, Don’t Ask 원칙과도 연결된다고 느꼈습니다. 물론 현실적으로 외부에 자동차 정보를 넘겨야 하는 상황은 있습니다. 그럴 때는 내부 리스트를 그대로 넘기기보다, 의도가 드러나는 메서드를 제공할 수 있습니다. public List<String> names() {
return cars.stream()
.map(Car::name)
.toList();
}
// 또는 우승자 이름이 필요하다면 이런 식으로 표현할 수 있습니다.
public List<String> winnerNames() {
return winners().stream()
.map(Car::name)
.toList();
}이렇게 하면 외부는 Cars의 내부 구조를 알 필요 없이, 정말 리스트 형태로 넘겨야 한다면 방어적 복사나 불변 컬렉션을 사용할 수도 있습니다. public List<Car> getCars() {
return List.copyOf(cars);
// 또는 return Collections.unmodifiableList(cars);
}다만 이것도 어디까지나 외부 변경을 막기 위한 안전장치일 뿐이고, 레벨 1 자동차 미션에서 느낌점미션 경험 기준으로도 일급 컬렉션을 도입했을 때 좋았던 점은, 처음에는 List를 들고 있는 외부 코드에서 반복문을 돌며 이동시키고, 위치를 비교하고, 우승자를 구하게 됩니다.
외부에서는 경주의 세부 계산 방식보다 “자동차들이 이동한다”, “결과를 구한다”는 흐름만 보게 됩니다. 그 결과 코드의 의도가 더 잘 드러나고, 언제 일급컬렉션을 도입할까다만 일급 컬렉션을 무조건 만들어야 한다고 생각하지는 않습니다. 단순히 값을 전달하기만 하는 DTO이거나, 저는 아래 경우에 일급 컬렉션 도입을 고민해볼 것 같습니다.
최종 정리결론적으로 저는 일급 컬렉션을 이렇게 정리할 수 있을 것 같습니다. 일급 컬렉션은 컬렉션을 감싼 객체가 아니라, List는 단순히 자동차들을 담는 자료구조에 가깝지만, 따라서 일급 컬렉션을 사용하는 이유는 단순히 포장 클래스를 만들기 위해서가 아니라, |
Beta Was this translation helpful? Give feedback.
-
|
일급컬렉션은 컬렉션을 사용하는 상황에서 변수로 사용하지 않고, 클래스로 한번 감싼 것입니다. 일급 컬렉션을 사용하는 이유를 생각해보면 아래와 같이 3가지 정도 생각할 수 있습니다. 1. 사용하는 컬렉션에서의 중복된 비즈니스 로직 처리로또에서 로또들의 숫자들을 다룰 때
카드들의 리스트를 다룰 때,
2. 컬렉션의 불변성 보장
결국 외부에세 Lottos에 메시지를 보내면 그 메시지 요청을 받은 객체 내부에서는 내부에 컬렉션을 처리할 자율적인 존재로서 제한을 둘 수가 있다. 3. 상태와 행위를 한곳에서 관리하며 적절한 비즈니스적인 역할 부여기존에 원시 컬렉션만 사용한다면 객체지향에서 역할, 책임, 협력을 지향하기 위해서라면 상태와 행위를 같은 곳에서 관리하는게 객체지향적이라고 생각이 드는데요, 원시 컬렉션을 사용하는 것보다 일급 컬렉션을 사용한다면 상태와 행위를 한 클래스에서 관리하고 그 컬렉션에 해당하는 비즈니스적인 로직을 생성시에 검증 후 생성하고, 내부 로직도 응집도있게 구성하고, 데이터를 조회할 때에도 불변성을 지키며 방어적 복사도 가능하게 된다. 결국, 일급 컬렉션을 사용함으로써, 해당 비즈니스 로직에 대한 응집도있는 설계를 가능하게 할 수 있다고 볼 수 있을거 같다. |
Beta Was this translation helpful? Give feedback.
-
1. 일급 컬렉션은 무엇인가?일급 컬랙션이란 다른 객체를 컬랙션 형태로 감싸고 있는 필드를 하나만 둔 클래스입니다. 2. 왜 사용하는가?처음에는 굳이 객체를 한번 더 다른 클래스로 감싸면, 큰 이점없이 그 값을 꺼내기 힘들기만 한데 왜 사용하는지 의문이 들었습니다. 예를들어 로또 미션을 예시로 들어보겠습니다. 🤔
🤔그렇다면
결론적으로는 |
Beta Was this translation helpful? Give feedback.
-
일급 컬렉션이란 무엇인가?일급 컬렉션은 컬렉션을 필드로 하나만 가지는 객체를 의미합니다. 단순히 List를 그대로 사용하는 대신, Casrs라는 객체로 감싸서 해당 컬렉션과 관련된 책임을 객체 안으로 캡슐화하는 방식입니다. 일급 컬렉션을 사용하는 이유일급컬렉션을 사용하는 이유는 컬렉션과 관련된 비즈니스 규칙을 하나의 객체에서 관리하기 위함입니다. 예를 들어, 자동차 경주 미션에서 자동차 이름 중복 검증, 자동차 수 검증, 우승자 계산과 같은 로직을 일급컬렉션을 사용하지 않고 List를 들고 다니며 작성하다면, 여러 서비스 메서드에 흩어질 수 있습니다. 이와 반대로 일급컬렉션을 사용한다면 이 모든 로직을 Cars내부로 응집할 수 있습니다. (메서드 예시: Cars.findWinner(), Cars.isDuplicatedName(), 등) 결국 컬렉션을 데이터의 묶음인 자료형이 아닌 하나의 도메인 객체로 간주하고 비즈니스 로직을 응집화 할 수 있습니다. 일급 컬렉션 사용시 좋았던 점첫번째로, 검증 로직이 응집화 되어 용이했습니다. 로또 미션에서는 List로 로또의 전체 번호를 관리했어야 했는데, 이와 관련된 검증로직(로또 번호가 6개인지, 중복이 없는지, 범위가 맞는지 확인)이 한 객체 내부에 모였고, LottoNumbers 생성시점에 검증이 완료될 수 있었습니다. 두번째로 도메인 로직의 위치가 자연스러워졌습니다. 우승자 찾기, 로또 번호 매칭 개수 계산, 카드 점수 합산처럼 컬렉션 전체를 기준으로 판단해야 하는 로직을 단일 객체인 LottoNumber보다 컬렉션 객체가 가지는 편이 더 자연스러웠습니다. 이 덕분에 서비스 계층이 절차적으로 길어지는 것을 줄일 수 있었습니다. 마지막으로 외부에서 컬렉션을 내부 원소를 수정하지 못하게 방지할 수 있었습니다. List를 그대로 반환하거나 공유하면 외부에서 add나 remove와 같은 메서드를 통해 상태를 바꿀 위험이 있습니다. 일급 컬렉션 내부에서 방어적 복사나 불변 리스트를 사용하면 객체의 상태를 더 안전하게 관리할 수 있었습니다. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Beta Was this translation helpful? Give feedback.
All reactions