원시값 포장과 일급 컬렉션

Seogi·2024년 4월 11일

객체지향 생활 체조 원칙에 이와 같은 원칙이 있다.

모든 원시값과 문자열을 포장한다

원시값과 문자열을 포장한다는 것이 뭘까?

val age: Int = 19

위와 같이 변수를 선언해준 것을 원시타입 변수를 선언해주었다고 한다.

포장은 단순하게 위 코드를 감싸주면 된다.

class Age(val age: Int)

fun main() {
    val age: Age = Age(19)
}

원시값을 포장한다는 것은 원시값을 감싸줌으로써 새로운 자료구조를 만들어준다 생각할 수 있다.

실제로 어떻게 사용이 되는지 코드를 통해 확인해보자.

필자가 과거에 작성한 코드이다.

class Lotto(private val numbers: List<Int>) {
    init {
        require(numbers.size == 6) //로또의 번호가 6개인지 확인
        require(numbers.all { it in 1..45 }) // 로또 번호가 1~45사이인지 확인
        require(numbers.distinct().size == numbers.size) // 중복된 로또번호는 없는지 확인
    }

    // 기타 로직들..
}

객체의 역할과 책임은 잠시 내려두고 보자면 동작하는 데 큰 문제는 없는 코드이다.

그런데 만약 다른 곳에서 Lottonumbers에서 각 숫자에 접근할 일이 있다면 어떨까?
사용자는 각 숫자가 1~45사이에 있다는 확신을 가질 수 있을까? 그 전에 로또 번호가 1~45사이여야 한다는 것을 인지는 할 수 있을까?

이런 경우가 여러 늘어난다면 불필요한 중복 코드가 많아지고 개발을 함에 있어서 더 많은 시간이 필요해질 것이다.

이 상황에서 로또 번호를 포장할 경우 로또 번호에 대한 객체인 LottoNumber가 생긴다면 로또번호에 대한 로직(유효성 검사 등)은 LottoNumber에서만 이루어지면 될 것이다. 또한 처음 코드를 본 사람도 LottoNumber를 보게되면 로또번호에 대한 규칙을 알 수 있다.

class LottoNumber(val number: Int) {
    init {
        require(number in 1..45) // 로또 번호가 1~45사이인지 확인
    }
}

class Lotto(private val numbers: List<LottoNumber>) {
    init {
        require(numbers.size == 6) //로또의 번호가 6개인지 확인
        require(numbers.distinct().size == numbers.size) // 중복된 로또번호는 없는지 확인
    }

    // 기타 로직들..
}

원시값을 포장함으로써 얻을 수 있는 장점을 정리해 보자면

  • 자신의 상태를 객체 스스로 관리할 수 있다.
  • 코드의 유지보수에 도움이 된다.
  • 코드 가독성 향상

원시값 포장에 대해선 간략히 알아보았고 일급 컬렉션은 뭘까?

사실 둘은 별로 다르지 않다.

원시값을 포장하면 원시값 포장이고 컬렉션을 포장하면 일급 컬렉션인 것이다.

엄밀히 말하면 Collection을 Wrapping하면서, 그 외 다른 멤버 변수가 없는 상태를 일급 컬렉션이라 한다.

앞서 사용했던 코드를 다시 보자면

class Lotto(private val numbers: List<LottoNumber>) {
    init {
        require(numbers.size == 6) //로또의 번호가 6개인지 확인
        require(numbers.distinct().size == numbers.size) // 중복된 로또번호는 없는지 확인
    }

    // 기타 로직들..
}

List<LottoNumber>Lotto라는 클래스로 감싸고 있고 그 외의 멤버변수가 존재하지 않기에 이미 일급 컬렉션인 것 을 알 수 있다.

그렇다면 일급 컬렉션을 사용하면 얻을 수 있는 장점은 무엇일까?

비지니스 로직에 종속적인 자료구조

로또는 번호가 6개(보너스 번호는 제외)이어야 하고 중복된 숫자가 없어야 한다는 조건이 있다. 따라서 로또 번호가 필요한 곳에서 이 규칙이 적용되어야 할 것이다.

그렇다면 그때마다 검증로직을 작성하는 것이 맞을까?

당연히 아니다.

우리는 중복된 검증로직을 작성하기 보다는 위 조건을 만족하는 자료구조를 만들면 될 것이고 Lotto 클래스가 그 예시이다.

상태와 행위를 한 곳에서 관리한다

Lotto 클래스는 상태와 행위를 한 곳에서 관리하기 때문에 같은 로직에 대해서 외부에서 구현을 할 필요가 없으므로 보일러 플레이트 코드와 중복을 피할 수 있다.

컬렉션의 불변을 보장한다

Lotto 클래스는 주 생성자를 통해 numbers가 생성된다.
이때 numbers는 List<T>로 임의로 추가, 삭제, 변경이 불가능하므로 불변성을 보장한다고 할 수 있다.

그래서 언제 일급 컬렉션의 사용을 고려하면 좋을까?

비즈니스 로직이 포함되거나 검증로직이 필요한 경우에는 충분히 생성을 고려할 수 있다.

위와 같은 경우가 아니라 그저 무분별하게 사용할 경우 불필요하게 객체를 생성하게 됨으로써 메모리 낭비, 성능저하를 불러일으킬 수 있다.

0개의 댓글