1. 일급 컬렉션이란?
일급 컬렉션 (First Class Collection) 은 소트웍스 앤솔로지의 객체지향 생활체조 규칙 8. 일급 콜렉션 사용에서 언급되었다.
규칙 8 : 일급 콜렉션 사용
이 규칙의 적용은 간단하다. 콜렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다.
각 콜렉션은 그자체로 포장돼어 있으므로 이제 콜렉션과 관련된 동작은 근거지가 마련된 셈이다.
필터가 이 새 클래스의 일부가 됨을 알 수 있다. 필터는 또한 스스로 함수 객체가 될 수 없다.
새 클래스는 두 그룹을 같이 묶는다든가 그룹의 각 원소에 규칙을 적용하는 등의 동작을 처리할 수 있다.
이는 인스턴스 변수에 대한 규칙의 확실한 확장이지만 그 자체를 위해서도 중요하다.
콜렉션은 실로 매우 유용한 원시 타입이다. 많은 동작이 있지만 후임 프로그래머나 유지보수 담당자에 의미적 의도나 단초는 거의 없다.
요약하자면 Collection을 Wrapping 하면서, Wrapping 한 Collection 외 다른 멤버 변수가 없는 상태를 일급 컬렉션이라고 한다.
이를 통해 우리는 다음과 같은 이점을 가지게 된다.
- 비즈니스에 종속적인 자료구조
- 상태와 행위를 한 곳에서 관리
- Collection의 불변성 보장
- 이름이 있는 컬렉션
2. 비즈니스에 종속적인 자료구조
예를들어 해당 서비스를 사용하는 사용자에게 쿠폰 서비스를 한다고 가정하겠습니다.
쿠폰 서비스는 다음과 같은 조건이 있다.
- 사용자는 10개의 쿠폰만 가질수 있다.
- 사용자는 중복되는 쿠폰을 가질수 없다.
위 두조건을 만족시키는 CouponService 서비스 메소드를 작성해보면
public class Coupon {
private String name;
private String discountRate;
public String getName() {
return name;
}
public String getDiscountRate() {
return discountRate;
}
}
public class CouponService {
private static final int COUPON_MAX_SIZE = 10;
public void createCouponList(){
List<Coupon> coupons = createNonDuplicateCoupons();
validateCouponsSize(coupons);
validateCouponsDuplicate(coupons);
}
private void validateCouponsSize(List<Coupon> coupons) {
if (coupons.size() > COUPON_MAX_SIZE){
throw new IllegalArgumentException("쿠폰은 10개까지만 가질수 있습니다.");
}
}
private void validateCouponsDuplicate(List<Coupon> coupons) {
Set<Coupon> nonDuplicateCoupon = new HashSet<>();
if (nonDuplicateCoupon.size() != coupons.size()){
throw new IllegalArgumentException("중복되는 쿠폰을 가질수 없습니다.");
}
}
private List<Coupon> createNonDuplicateCoupons() {
return new ArrayList<Coupon>();
}
}
위와 같이 서비스 메소드에서 비즈니스 로직을 수행하였다.
하지만 이렇게 코드를 작성하는 경우 문제점이 발생한다.
- List<Coupon> 객체로된 데이터는 모두 검증 로직을 사용해야 하는가?
- 신규 입사자가 어떻게 검증로직이 필요한지 알 수 있는가?
내가 필요한 조건을 만족하는 자료구조를 만들수 없을까?
이러한 의문점에서 나온 개념이 바로 1급 컬렉션이다.
즉, 우리의 제약조건인 10개의 쿠폰만 담아둘 수 있으며, 중복된 쿠폰을 담지 못하는 자료구조를 직접 만들어서 사용하는 것이다.
한번 일급 컬렉션 클래스를 만들어보자.
public class CouponBasket {
private static final int COUPON_MAX_SIZE = 10;
private final List<Coupon> coupons;
public CouponBasket(List<Coupon> coupons) {
validateCouponsSize(coupons);
validateCouponsDuplicate(coupons);
this.coupons = coupons;
}
private void validateCouponsSize(List<Coupon> coupons) {
if (coupons.size() > COUPON_MAX_SIZE){
throw new IllegalArgumentException("쿠폰은 10개까지만 가질수 있습니다.");
}
}
private void validateCouponsDuplicate(List<Coupon> coupons) {
Set<Coupon> nonDuplicateCoupon = new HashSet<>();
if (nonDuplicateCoupon.size() != coupons.size()){
throw new IllegalArgumentException("중복되는 쿠폰을 가질수 없습니다.");
}
}
}
만든 일급 컬랙션 객체를 사용해서 비즈니스 로직에 사용하면
public class CouponService {
private static final int COUPON_MAX_SIZE = 10;
public void createCouponList(){
CouponBasket couponBasket = new CouponBasket(createNonDuplicateCoupons());
}
private List<Coupon> createNonDuplicateCoupons() {
return new ArrayList<Coupon>();
}
}
다음과 같이 비즈니스에 종속적인 자료구조가 만들어 지면서 발생할 문제가 최소화 되었다.
3. 상태와 행위를 한 곳에서 관리
그렇다면 List<Coupon> coupons 의 원소중에서 내가 원하는 쿠폰을 조회하기 위해서 find 메서드를 만든다고 가정해 보면
만약 비즈니스 로직에서 find 행위를 수행하는 메소드를 작성하면, List<Coupon> 을 사용하는 모든 영역에서 중복되는 find 메소드를 작성해야 할 것이다.
이런것을 막아주는 역할 또한 일급 컬렉션이 수행할 수 있다.
즉, 멤버변수의 상태와 필요한 로직을 수행하는 행위를 일급 컬렉션 클래스에서 한번에 관리하는 것이다.
위에서 작성한 일급 컬렉션 객체에 find 메소드를 추가해보자
public class CouponBasket {
private static final int COUPON_MAX_SIZE = 10;
private final List<Coupon> coupons;
public CouponBasket(List<Coupon> coupons) {
validateCouponsSize(coupons);
validateCouponsDuplicate(coupons);
this.coupons = coupons;
}
private void validateCouponsSize(List<Coupon> coupons) {
if (coupons.size() > COUPON_MAX_SIZE){
throw new IllegalArgumentException("쿠폰은 10개까지만 가질수 있습니다.");
}
}
private void validateCouponsDuplicate(List<Coupon> coupons) {
Set<Coupon> nonDuplicateCoupon = new HashSet<>();
if (nonDuplicateCoupon.size() != coupons.size()){
throw new IllegalArgumentException("중복되는 쿠폰을 가질수 없습니다.");
}
}
public Coupon find(String name){
return coupons.stream()
.filter(coupon -> coupon.getName().equals(name))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("일치하는 쿠폰이 없습니다."));
}
}
4. Collection 의 불변성 보장
일급 컬렉션 클래스는 상태와 행위를 한곳에서 처리하기 때문에 가변객체인 Collection 객체를 불변으로 만들 수 있다.
객체를 불변으로 만드는 것에대해서는 이전에 올렸던 포스팅을 참고하길 바란다.
불변객체(Immutable Object)를 사용해야하는 이유
일급 컬렉션 클래스를 불변 객체로 만들어 보자.
public class CouponBasket {
private static final int COUPON_MAX_SIZE = 10;
//변수를 final 로 선언
private final List<Coupon> coupons;
public CouponBasket(List<Coupon> coupons) {
validateCouponsSize(coupons);
validateCouponsDuplicate(coupons);
//방어적 복사
this.coupons = new ArrayList<>(coupons);
}
public List<Coupon> getCoupons() {
//UnmodifiableList 로 객체 복사
return Collections.unmodifiableList(coupons);
}
private void validateCouponsSize(List<Coupon> coupons) {
if (coupons.size() > COUPON_MAX_SIZE){
throw new IllegalArgumentException("쿠폰은 10개까지만 가질수 있습니다.");
}
}
private void validateCouponsDuplicate(List<Coupon> coupons) {
Set<Coupon> nonDuplicateCoupon = new HashSet<>();
if (nonDuplicateCoupon.size() != coupons.size()){
throw new IllegalArgumentException("중복되는 쿠폰을 가질수 없습니다.");
}
}
public Coupon find(String name){
return coupons.stream()
.filter(coupon -> coupon.getName().equals(name))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("일치하는 쿠폰이 없습니다."));
}
}
참조 : https://jojoldu.tistory.com/412
불변성 부분은 저의 의견을 조금더 넣어서 작성하였습니다.
'JAVA' 카테고리의 다른 글
JAVA 의 Garbage Collection (0) | 2022.04.14 |
---|---|
Volatile 을 사용한 가시성 보장 (0) | 2022.03.15 |
쓰레드의 지역변수 ThreadLocal (0) | 2022.03.15 |
String , StringBuilder, StringBuffer 속도차이 비교 (0) | 2022.02.09 |
Equals 와 HashCode (0) | 2022.02.09 |
방어적 복사 vs Unmodifiable Collection (0) | 2022.02.08 |