자바를 공부하던 중 객체를 담는 List를 생성하는 클래스를 짰는데 이러한 피드백을 받았다.
public class CarFacility {
public CarFacility() {
}
public List<Car> buildCar(int carNum) {
List<Car> carList = new ArrayList<>();
for (int i = 0; i < carNum; i++) {
carList.add(new Car());
}
return carList;
}
}
일급 콜렉션이 뭐지?
일급 컬렉션은 어딘가 한 번 들어본듯하기만 했던 생소한 개념이었다. 잘 모르는 개념을 마주친 이상 이것을 한번 파고들어 보기로 했다.
그래서 일단 정의에 대해 찾아봤다
규칙 8 : 일급 콜렉션 사용
이 규칙의 적용은 간단하다. 콜렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다.
각 콜렉션은 그자체로 포장돼어 있으므로 이제 콜렉션과 관련된 동작은 근거지가 마련된 셈이다.
필터가 이 새 클래스의 일부가 됨을 알 수 있다. 필터는 또한 스스로 함수 객체가 될 수 없다.
새 클래스는 두 그룹을 같이 묶는다든가 그룹의 각 원소에 규칙을 적용하는 등의 동작을 처리할 수 있다.
이는 인스턴스 변수에 대한 규칙의 확실한 확장이지만 그 자체를 위해서도 중요하다.
콜렉션은 실로 매우 유용한 원시 타입이다. 많은 동작이 있지만 후임 프로그래머나 유지보수 담당자에 의미적 의도나 단초는 거의 없다. - 소트웍스 앤솔러지의 객체지향 생활체조 파트에서
이것만 봐서는 확실한 이해가 가지 않는다. 이분 의 블로그를 참고하니 이런 정의를 내리고 계셨다
Collection을 Wrapping하면서, 그 외 다른 멤버 변수가 없는 상태를 일급 컬렉션이라 합니다
Collection을 Wrapping 한다라... 그럼 먼저 이 말이 무엇을 의미하는지 알아야 할 것 같다.
Collection은 무엇이고 Wrapping은 무엇일까? 그건 왜 쓸까?
Java에서는 데이터의 집합, 그룹을 컬렉션이라 한다. 우리가 자주 쓰는 List나 Set 자료구조가 이에 해당한다.
그런데 이 컬렉션에 담을 수 있는 것은 오직 객체다. 따라서 int, char 등의 원시(primitive) 타입은 컬렉션에 담을 수 없다.
컬렉션에 원시 타입을 담으려면 Wrapper 클래스를 통해 원시 타입을 Boxing해 객체화화여 담아야 한다. 그리고 컬렉션에서 값을 꺼낼 때, 다시 원시 타입으로 unBoxing 하여 뽑아낸다.
따라서 이와 같은 상황처럼 데이터를 객체로 표현하기 위해 포장해주는 것이 Wrapper 클래스다.
그렇다면 컬렉션을 Wrapping 한다는 것의 의미는?
이전의 예시에서 원시 타입을 객체로 포장했다면, 이제는 컬렉션 자료구조 자체를 객체로 포장하는 것이 컬렉션을 Wrapping 하는 것이다.
이 블로그에서 참고한 예시를 들면
이 코드를
public class Person {
private String name;
private List<Car> cars;
// ...
}
public class Car {
private String name;
private String oil;
// ...
}
이렇게 바꾼다
public class Person {
private String name;
private Cars cars;
// ...
}
// List<Car> cars를 Wrapping
// 일급 컬렉션
public class Cars {
private List<Car> cars;
// ...
}
public class Car {
private String name;
private String oil;
// ...
}
차이가 보이는가?
이전에는
private List<Car> cars;
였던 부분이
private Cars cars;
이렇게 컬렉션이 아닌 하나의 객체로 표현되고 있다. 즉, 컬렉션이 Wrapping 된 것이다.
그리고 그 Wrapping 된 Cars 클래스를 보면 다음과 같이 구현되어 있다.
// List<Car> cars를 Wrapping
// 일급 컬렉션
public class Cars {
// 멤버변수가 하나
private List<Car> cars;
// ...
}
자, 이 클래스에서는 단 하나의 컬렉션
List<Car> cars;
말고는 다른 아무 멤버변수도 존재하지 않는다.
위에서 한 번 언급 했지만, 위와 같이 컬렉션을 Wrapping 하면서 다른 멤버 변수가 하나도 없는 것. 이것이 일급 컬렉션이다.
솔직히 처음 봤을 땐...이걸 굳이 왜 하지...? 싶은 의문이 들었다. 하지만 이분의 블로그를 참고하며 다음과 같은 이점을 가짐을 알 수 있었다.
List<Car> cars = createCars();
validateCars(cars)
//이후 로직 진행
물론 다음과 같이 검증 로직을 진행할 수도 있겠지만
List<Car> cars
다음과 같은 리스트 콜렉션이 필요한 로직이 무수히 많다면? 그럼 cars 라는 리스트를 생성할 때 마다 일일이 검증을 할 것인가? 물론 그렇게 할 수도 있겠지만...이 경우 코드의 중복이 너무 많아진다.
그렇다면 아예 해당 조건의 검증이 완료된 상태여야만 컬렉션을 생성할 수 있는 자료구조가 있다면, 그리고 이 자료구조로 컬렉션을 생성한다면, 쓸데 없는 코드가 없어지지 않을까?
말인 즉슨
public class Cars {
private static final int CAR_MAX_SIZE= 5;
private final List<Car> cars;
public Cars (List<Car> cars){
validateCars(cars);
this.cars = cars;
}
private void validateCars(List<Car> cars){
for ( Car car : cars ){
if (car.name.length() > 5){
throw new IllegalArgumentException("차량 이름 길이는 5를 넘을 수 없습니다")
}
}
}
}
이런 식으로 애초에 차량 이름이 5 이하인 차량의 컬렉션만 생성할 수 있는 자료구조를 만들고 나니
Cars cars = new Cars(createCars());
내가 원하는 비즈니스 요구 사항에 필요한 자료구조가 자동으로 튀어나오니 매우 편리할 수 밖에 없다. 즉 비즈니스에 종속적인 자료구조가 만들어지는 것이다.
또 이런 식으로 Cars가 필요한 모든 로직에 이 일급 컬렉션만 있으면 일일이 검증을 하지 않아도 되니 매우 편리하다! 즉 멤버변수의 상태(이름 길이)와, 필요한 로직을 수행하는 행위(validate)을 일급 컬렉션에서 한 번에 관리해 중복을 줄일 수 있다.
또한 상태와 행위를 오직 일급 컬렉션에서만 관리하므로, 객체를 변경시킬 수 있는 메소드를 이곳에서 만들지 않아버리면 가변객체인 컬렉션을 불변하게 만들 수 있다.
객체를 왜 불변하게 만들면 안되는지에 대한 설명은 이곳을 참고하겠다
사소하지만 컬렉션에 이름을 붙일 수 있으므로, 만약 어떤 특정 카드사 비즈니스에 종속된 일급 컬렉션을 만들 때,이름 네이밍으로 좀 더 알기 쉽게 구분한다는 장점이 있다
예를 들어 BC카드에서 필요한 List를 만든다면,
BCCards bccards = new BCCards(createBCCards());
삼성카드에서 필요한 List를 만든다면
SamSungCards samsungcards = new SamsungCards(createSaMSungCards());
이런 식으로 네이밍을 통해 컬렉션 기반으로 용어사용과 검색을 할 수 있다.
이전까지 생소한 개념으로만 알고 있었던 일급 콜렉션에 대해 알고, 더 중요한 것은 이게 왜 필요한지에 대해 조금이나마 체감할 수 있었다. 앞으로 클린 코드를 작성하기 위해 컬렉션 자료구조를 생성하는 경우 일급컬렉션에 대해 반드시 고려해야할 것이다.
https://jojoldu.tistory.com/412
https://dev-cool.tistory.com/28
https://dev-cool.tistory.com/23
https://tecoble.techcourse.co.kr/post/2020-05-08-First-Class-Collection/