Java

일급 컬렉션

99duuk 2024. 10. 21. 11:00

| 일급 컬렉션(First-Class Collection)

일급 컬렉션은 컬렉션을 포함한 클래스를 만들어 사용하는 방식을 말함

이 클래스는 해당 컬렉션 하나만 필드로 가지며, 그 컬렉션과 관련된 동작을 함께 정의함

 

 

- 단일 책임 원칙: 일급 컬렉션은 하나의 컬렉션에 대한 책임만 가짐

- 불변성: 컬렉션의 불변성을 보장할 수 있음

- 상태와 행위의 결합: 컬렉션과 관련된 동작을 한 곳에서 관리

- 비즈니스 로직 캡슐화: 컬렉션과 관련된 비즈니스 로직을 해당 클래스 내에 캡슐화

- 이름이 있는 컬렉션: 컬렉션의 의도를 명확히 표현할 수 있음

 

일급 컬렉션은 마치 특별한 용도의 상자와 같습니다. 일반 컬렉션이 그냥 물건을 담는 평범한 상자라면, 일급 컬렉션은 특정 종류의 물건만 담을 수 있고, 그 물건들을 어떻게 다뤄야 하는지 설명서가 함께 붙어있는 특별한 상자입니다.

 

 

public class LottoNumbers {
    private final List<Integer> numbers;

    public LottoNumbers(List<Integer> numbers) {
        validate(numbers);
        this.numbers = new ArrayList<>(numbers);
    }

    private void validate(List<Integer> numbers) {
        if (numbers.size() != 6) {
            throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
        }
        if (new HashSet<>(numbers).size() != 6) {
            throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다.");
        }
        if (numbers.stream().anyMatch(n -> n < 1 || n > 45)) {
            throw new IllegalArgumentException("로또 번호는 1부터 45 사이여야 합니다.");
        }
    }

    public boolean contains(int number) {
        return numbers.contains(number);
    }

    public int matchCount(LottoNumbers other) {
        return (int) this.numbers.stream()
                .filter(other.numbers::contains)
                .count();
    }

    @Override
    public String toString() {
        return numbers.toString();
    }
}

 

 

LottoNumbers는 로또 번호 리스트를 감싸는 일급 컬렉션임

생성자에서 유효성 검사를 수행하여 항상 올바른 로또 번호만 생성되도록 함

contains 메서드로 특정 번호의 포함 여부를 확인할 수 있음

matchCount 메서드로 다른 로또 번호와의 일치 개수를 쉽게 계산할 수 있음

 

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
LottoNumbers lotto = new LottoNumbers(numbers);

System.out.println(lotto); // [1, 2, 3, 4, 5, 6]
System.out.println(lotto.contains(3)); // true

LottoNumbers otherLotto = new LottoNumbers(Arrays.asList(4, 5, 6, 7, 8, 9));
System.out.println(lotto.matchCount(otherLotto)); // 3

 

 

로또 번호와 관련된 모든 로직을 한 곳에서 관리할 수 있고, 재사용성과 유지보수성이 향상됨

 


기존 단순 클래스 방식: 일반적으로 List<Integer>나 Integer[]를 사용할 경우

List<Integer> lottoNumbers = Arrays.asList(1, 2, 3, 4, 5, 6);

// 데이터 검증
if (lottoNumbers.size() != 6) {
    throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
}
// ... 다른 검증 로직들 ...

// 특정 번호 포함 여부
boolean contains = lottoNumbers.contains(3);

// 일치 개수 계산
List<Integer> otherNumbers = Arrays.asList(4, 5, 6, 7, 8, 9);
int matchCount = (int) lottoNumbers.stream()
        .filter(otherNumbers::contains)
        .count();

로또 번호를 사용할 때마다 검증, 포함 여부 확인, 일치 개수 계산 등의 로직을 반복해서 작성해야 함

-> 내부 상태에 직접 접근

 

| 일급 컬렉션 방식: LottoNumbers 클래스를 사용할 경우

LottoNumbers lotto = new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 6));
// 생성 시점에 이미 데이터 검증 완료

boolean contains = lotto.contains(3);

LottoNumbers otherLotto = new LottoNumbers(Arrays.asList(4, 5, 6, 7, 8, 9));
int matchCount = lotto.matchCount(otherLotto);

생성 시점 검증: 객체가 생성될 때 자동으로 데이터 검증이 이루어집니다. 잘못된 데이터로 객체가 생성되는 것을 방지함

캡슐화: 로또 번호와 관련된 모든 동작(검증, 포함 여부 확인, 일치 개수 계산 등)이 LottoNumbers 클래스 내부에 캡슐화되어 있음

재사용성: 한 번 정의해 놓은 로직을 여러 곳에서 쉽게 재사용할 수 있음

가독성: 코드의 의도가 더 명확해지며, 비즈니스 로직을 이해하기 쉬워짐

유지보수성: 로또 번호와 관련된 요구사항이 변경될 경우, LottoNumbers 클래스만 수정하면 됨

->  객체 내부에서 판단하고 결과만 반환

 

 

∴ 따라서, 일급 컬렉션을 사용하면 로또 번호와 관련된 모든 책임을 한 클래스에 모아 관리할 수 있게 됨

만약 로또 번호가 6개에서 7개로 변하고, 중복될 수 있게 기준이 변경된다면 

-> 단순 클래스  : 모든 사용 지점 수정 필요

-> 일급 컬렉션 : LottoNumbers 클래스의 validate 메서드만 수정


| 장점 

 

- 불변성(Immutability) 보장

일급 컬렉션은 내부 컬렉션을 final로 선언하고, 생성자에서 새로운 컬렉션을 만들어 할당함으로써 불변성을 쉽게 보장할 수 있음

 

- 상태와 행위의 응집

데이터(로또 번호)와 그 데이터를 조작하는 메서드(검증, 비교 등)가 한 클래스에 모여 있어 응집도가 높아짐

 

- 테스트 용이성

로또 번호와 관련된 로직을 테스트할 때, LottoNumbers 클래스만 집중적으로 테스트하면 됨

 

- 확장성

나중에 로또 규칙이 변경되거나 새로운 기능이 추가되어도 LottoNumbers 클래스만 수정하면 됨

 

- 명확한 네이밍

List<Integer>보다 LottoNumbers라는 이름이 더 명확하게 의도를 전달함