티스토리 뷰

일급 컬렉션 First-Class Collection

일급 컬렉션은 컬렉션을 하나의 클래스로 감싸고 컬렉션 자체를 하나의 객체로 다루는 것을 의미한다.

 

즉 클래스에 컬렉션만 필드로 하나를 가지는 것!

 

일급(First-Class)이라는 용어는

1.컬렉션을 단일 객체로 취급하고,

2.컬렉션을 사용한 비즈니스 로직 관리와 데이터 무결성

을 지키려는 의도를 가지고 있다.

 

일급 컬렉션을 사용하는 이유

1. 책임 분리

컬렉션과 관련된 로직을 일급 컬렉션 내부로 캡슐화해서 데이터 관련된 로직을 한곳에서 관리할 수 있다.

컬렉션과 관련된 로직을 일급 컬렉션에 위임할 수 있게 된다.

 

학생 데이터를 가지는 Student 클래스가 있다고 가정하자.

이 때, 전체 학생을 관리하기 위해서 학생 리스트 컬렉션을 만들 수 있다.

 

그렇다면 전체 학생 리스트는 어디서 관리할 수 있을까?

만약 StudentGroup 라는 이름의 클래스에서 전체 학생 리스트 List<Student> students를 클래스의 필드로 두자.

그리고 리스트를 외부에 노출하지 않고 컬렉션과 관련된 로직을 해당 클래스 내부에서 관리해보자.

 

이렇게 리스트를 하나의 필드만 가지고, 리스트를 관리하는 비즈니스 로직을 가지면 일급 컬렉션이 된다. 👍🏻

 

 

이렇게 데이터에 관련된 로직을 내부에 정의함에 따라서 비즈니스 로직을 캡슐화 할 수 있는 장점도 있다.

// 자바에서 불변으로 만들기 위해 필드를 final로 선언하면 된다.
data class Student(val name: String, val score: Int)

class StudentGroup(private val students: List<Student>) {

    // 학생들의 평균 점수를 계산하는 비즈니스 로직
    fun getAverageScore(): Double {
        return students.map { it.score }.average()
    }

    // 학생 중에서 특정 점수 이상을 받은 학생을 필터링하는 비즈니스 로직
    fun getStudentsAboveScore(minScore: Int): List<Student> {
        return students.filter { it.score >= minScore }
    }

    // 전체 학생 리스트를 직접 제공하지 않고 특정 형식으로 반환
    fun getStudentNames(): List<String> {
        return students.map { it.name }
    }
}

 

2. 데이터 무결성 보장

컬렉션을 직접 외부에 노출하지 않기 때문에, 외부에서 컬렉션을 임의의 상태로 변경할 수 없도록 할 수 있다.

 

getStudentNames와 같은 메서드를 보면 리스트를 그대로 반환하지 않고,

필요한 데이터(학생 이름)만을 추출해서 반환하는 것을 볼 수 있다.

이를 통해 직접적인 접근을 막고, 또 getter() 메소드를 사용하지 않고 데이터를 제공할 수 있다.

 

 

하지만 일급 컬렉션을 불변 객체로 만들 수 있는 것이지,

일급 컬렉션 자체가 불변성을 지닌다고는 할 수 없다!

 

불변성을 지키면서 컬렉션 수정이 필요하다면, 새로운 일급 컬렉션 객체를 생성하도록 설계하면 된다.

 

 

 

일급 컬렉션을 불변 객체로 만드는 방법

불변성을 지키면서 새로운 일급 컬렉션 객체를 생성하는 방법은 다음과 같다.

 

방어적 복사 사용

생성자의 인자를 그대로 사용하지 않고, 새로운 리스트를 생성해 필드에 할당하는 것을 방어적 복사라고 한다.

 

이렇게 방어적 복사를 사용하면

외부에서 리스트를 변경해도 StudentGroup 객체 내부의 students 필드는 영향을 받지 않는다.

 

 

 

class StudentGroup(private val students: List<Student>) {

    // 새로운 학생을 추가할 때 기존 리스트를 수정하지 않고 새로운 객체를 생성!
    fun addStudent(newStudent: Student): StudentGroup {
        val newStudents = students + newStudent
        return StudentGroup(newStudents)
    }
}

Kotlin에서 List는 기본적으로 불변형이다..!!

그리고 + 연산자는 내부적으로 기존 요소를 복사한 후 새로운 요소를 추가해서 새로운 리스트를 반환한다.

 

 

자바에서는 생성자에서 불변 리스트로 초기화 할 필요가 있다.

class StudentGroup {
    privat final List<Student> students;

    // 생성자에서 불변 리스트로 초기화한다.
    public StudentGroup(List<Student> students) {
        this.student = Collections.unmodifiableList(new ArrayList<>(students))
    }

    // 새로운 학생을 추가하고, 새로운 StudentGroup 객체를 반환
    public StudentGroup addStudent(Student newStudent) {
        List<Student> newStudents = new ArrayList<>(students);
        newStudents.add(newStudent);
        return new StudentGroup(newStudents);
    }
}

 

생성자에서 원본 리스트를 수정할 수 없도록, 리스트를 복사하고 불변 리스트 unmodifiableList 로 감싸야한다.

 

그리고 새로운 학생 추가하는 메서드 addStudent() 에서 새로운 리스트를 담은 StudentGroup 객체를 반환한다.

 

 

 

그렇다면 불변 객체를 만드는 방법을 잘 지켰는지 확인해보자.

1. 객체의 상태를 변경할 수 있는 메서드를 제공하지 않는다.

StudentGroup에는 리스트를 수정하는 setter() 메서드가 없다.

또 addStudent() 메서드도 원본 객체를 수정하지 않고, 새로운 객체를 반환함으로써 불변성을 유지한다.

 

2. 모든 필드를 final로 선언한다.

students 리스트를 final로 선언하여 초기화 이후 변경되지 않는다.

 

3. 모든 필드를 private으로 선언한다.

students는 외부에서 직접 접근할 수 없으며, getter를 제공하지 않고 가공된 데이터를 반환하는 메서드만 제공하여 캡슐화를 유지한다.

 

4. 자신 외에 내부의 가변 컴포넌트에 접근할 수 없게 한다.

students 리스트는 생성자에서 복사 후 Collections.unmodifiableList로 감싸기 때문에 외부에서 리스트를 변경할 수 없다.

 

 

일급 컬렉션에서 불변 객체를 사용의 장점

1. 예측 가능한 코드

불변 객체는 상태 변화가 없기 때문에, 여러 위치에서 동일한 객체를 참조해도 원치않는 값 변경을 막을 수 있다.

따라서 코드의 흐름을 쉽게 따라갈 수 있다.

 

2. Thread-safe

객체의 상태가 변화하지 않기 때문에, 동기화 없이 안전하다.

따라서 멀티 스레드 환경에서 데이터 일관성을 유지할 수 있다.

 

3. 의도와 책임이 명확해짐

객체 내부에서 불변성을 책임지면서 해당 객체를 사용하는 외부에서는 변경이 발생하지 않을 것을 보장받는다.

 

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함