해당 포스팅은 Kotlin 언어를 기반으로 작성하였습니다.
모든 원시값과 문자열을 포장하자
우선, 모든 원시값과 문자열을 포장하자는 이야기에 대해 살펴보겠습니다.
Lotto와 관련된 프로그램을 작성한다면, 원시값을 아래와 같은 코드로 검증해 줄 수 있습니다.
val lottoNumber: Int = 45
class Lotto(lottoNumbers: List<Int>) : List<LottoNumber> by lottoNumbers {
init {
lottoNumbers.forEach { validateLottoNumber(it) }
validateLottoSize(lottoNumbers)
}
fun validateLottoNumber(lottoNumber: Int) {
require(lottoNumber in MIN_LOTTO_NUMBER..MAX_LOTTO_NUMBER) { "Error! 로또 번호의 범위는 1부터 45입니다." }
}
fun validateLottoSize(lottoNumbers: List<LottoNumber>) {
require(lottoNumber.size == LOTTO_SIZE) { "Error! 로또 번호는 6개여야 합니다." }
}
companion object {
private const val LOTTO_SIZE = 6
private const val MIN_LOTTO_NUMBER = 1
private const val MAX_LOTTO_NUMBER = 45
}
}
로또 번호는 1에서 45 사이의 범위에 속하기 때문에, 이를 벗어나는 숫자가 인자로 들어오면 IllegalArgumentException이 발생합니다.
값에 대한 검증도 해주고 있는데, 위 코드는 문제가 없는 것 아닐까요?
만약, 로또의 번호를 해당 Lotto 클래스가 아닌 다른 곳에서도 사용해야 한다면 매번 값의 범위를 검증해주어야 하는 문제가 발생합니다.
또한, 여기에서는 예시로 대중들이 흔히 알고 있는 Lotto라는 개념을 사용하지만, Lotto에 대한 개념이 없다고 가정했을 때 List의 Int가 의미하는 바가 무엇인지 파악하기에 다소 어려움이 존재합니다.
그 외에도, 깜빡하고 원시 타입인 로또 번호를 검증해주지 않았을 때 심각한 오류로 이어질 수 있었습니다.
또한, 로또의 번호를 검증하는 것이 과연 Lotto 클래스가 해야할 일인가 생각해 볼 필요가 있습니다.
현재 Lotto 클래스는 로또 번호 리스트의 개수 검증 + 로또 번호의 범위 검증이라는 2가지 일을 수행하므로, 관심사 분리가 제대로 되지 않았다고 할 수 있습니다.
val lottoNumber: Int = 45
// LottoNumber
value class LottoNumber(val number: Int) {
init {
validateLottoNumber(number)
}
fun validateLottoNumber(lottoNumber: Int) {
require(lottoNumber in MIN_LOTTO_NUMBER..MAX_LOTTO_NUMBER) { "Error! 로또 번호의 범위는 1부터 45입니다." }
}
}
// Lotto
class Lotto(lottoNumbers: List<LottoNumber>) : List<LottoNumber> by lottoNumbers {
init {
validateLottoSize(lottoNumbers)
}
fun validateLottoSize(lottoNumbers: List<LottoNumber>) {
require(lottoNumber.size == LOTTO_SIZE) { "Error! 로또 번호는 6개여야 합니다." }
}
companion object {
private const val LOTTO_SIZE = 6
private const val MIN_LOTTO_NUMBER = 1
private const val MAX_LOTTO_NUMBER = 45
}
}
로또 번호를 의미하는 Primitive type인 Int형 프로퍼티를 LottoNumber라는 클래스로 감싸주었습니다.
이제 로또 번호라는 개념이 필요할 때마다, 필요한 곳에서 검증 코드를 작성할 필요가 없어졌습니다.
원시 타입을 감싸줌으로써,
1. 보일러 플레이트 코드를 줄일 수 있다. (= 사용하는 곳에서마다 반복적인 검증 코드가 필요 없음)
2. Lotto의 관심사를 분리할 수 있다.
3. 응집도를 높일 수 있다.
4. 비즈니스에 종속적인 래퍼 클래스
5. 변수가 의미하는 바를 명확히 파악할 수 있다.
위와 같은 효과를 얻을 수 있게 되었습니다.
또한, 위 코드에서는 LottoNumber가 감싸고 있는 number 프로퍼티가 불변이므로, 언제든 값의 Consistency를 보장할 수 있습니다.
'CS > OOP' 카테고리의 다른 글
객체지향 프로그래밍 - OOP 상속(Inheritance) 이란? (1) | 2022.11.01 |
---|---|
객체지향 프로그래밍 - OOP 캡슐화(Encapsulation) 이란? (1) | 2022.10.20 |
객체지향 프로그래밍 - OOP 추상화(Abstraction) 이란? (2) | 2022.10.17 |