객체지향 생활 체조 원칙에 이와 같은 원칙이 있다.
모든 원시값과 문자열을 포장한다
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) // 중복된 로또번호는 없는지 확인
}
// 기타 로직들..
}
객체의 역할과 책임은 잠시 내려두고 보자면 동작하는 데 큰 문제는 없는 코드이다.
그런데 만약 다른 곳에서 Lotto의 numbers에서 각 숫자에 접근할 일이 있다면 어떨까?
사용자는 각 숫자가 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>로 임의로 추가, 삭제, 변경이 불가능하므로 불변성을 보장한다고 할 수 있다.
그래서 언제 일급 컬렉션의 사용을 고려하면 좋을까?
비즈니스 로직이 포함되거나 검증로직이 필요한 경우에는 충분히 생성을 고려할 수 있다.
위와 같은 경우가 아니라 그저 무분별하게 사용할 경우 불필요하게 객체를 생성하게 됨으로써 메모리 낭비, 성능저하를 불러일으킬 수 있다.