반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[이펙티브 코틀린] Item16. 프로퍼티는 동작이 아니라 상태를 나타내야 한다. 본문

Kotlin/이펙티브 코틀린

[이펙티브 코틀린] Item16. 프로퍼티는 동작이 아니라 상태를 나타내야 한다.

어리둥절범고래 2023. 10. 2. 18:57
반응형

기본적으로 코틀린의 프로퍼티는 사용자 정의 게터와 세터를 가질 수 있습니다.

var name: String? = null
    get() = field?.toUpperCase()
    set(value) {
        if (!value.isNullOrBlank()) {
            field = value
        }
    }

 

이 코드에서 field라는 식별자를 확인할 수 있습니다.

이는 프로퍼티의 데이터를 저장해두는 백킹 필드(backing field)에 대한 레퍼런스입니다.

val을 사용해서 읽기 전용 프로퍼티를 만들 때는 field가 만들어지지 않습니다.

val fullName: String
    get() = "$name $surname"

 

var를 이용한 프로퍼티는 게터와 세터를 정의할 수 있으며, 이를 파생 프로퍼티(derived property)라고 부릅니다.

이처럼 코틀린의 모든 프로퍼티는 디폴트로 캡슐화되어 있습니다.

 

프로퍼티를 통한 getter, setter접근은 필드와 다를 수 있습니다.

예를 들어 아래처럼 코드를 작성할 수도 있습니다.

var date: Date
    get() = Date(millis) // 매번 Date 객체를 생성합니다.
    set(value){
        // 객체는 millis 필드만 가집니다.
        millis = value.time
    }

 

프로퍼티는 필드가 필요없으며, 오히려 프로퍼티는 개념적으로 접근자(getter/setter)를 나타냅니다.

이러한 원리로 코틀린은 인터페이스에도 프로퍼티를 정의할 수 있습니다.

interface Person {
    val name: String
}

 

이는 게터를 가질 거라는 것을 나타내며, 다음과 같이 오버라이드할 수 있습니다.

open class SuperComputer {
    open val theAnswer: Long = 42
}
class AppleComputer : SuperComputer() {
    override val theAnswer: Long = 1_800_275_2273
}

 

마찬가지의 이유로 프로퍼티를 위임할 수도 있습니다.

val db: Database by lazy { connectDb() }

 

프로퍼티는 본질적으로 함수이므로, 확장 프로퍼티를 만들 수도 있습니다.

val Context.preferences: SharedPreferences
    get() = PreferenceManager
        .getDefaultSharedPreferences(this)

 

코드에서 확인할 수 있는 것처럼 프로퍼티는 필드에 접근할 수 있는 접근자를 나타낼 뿐입니다.

이처럼 프로퍼티를 함수 대신 사용할 수도 있지만, 이런 함수에 알고리즘의 동작을 나타내는 것은 좋지 않습니다.

// 가능은 하지만, 이렇게 쓰지 마세요!
val Tree<Int>.sum: Int
    get() = when(this){
        is Leaf -> value
        is Node -> left.sum + right.sum
    }

 

이러한 처리는 프로퍼티가 아니라 함수로 구현해야 합니다.

fun Tree<Int>.sum(): Int = when (this) {
    is Leaf -> values
    is Node -> left.sum() + right.sum()
}

 

원칙적으로 프로퍼티는 상태를 나타내거나 설정하기 위한 목적으로만 사용하는 것이 좋고, 다른 로직 등을 포함하지 않아야 합니다.

이러한 프로퍼티는 여러가지 오해와 잘못된 사용을 일으킬 수 있습니다.

구체적으로 프로퍼티 대신 함수를 사용하는 것이 좋은 경우를 정리해 보면, 다음과 같습니다.

 

  • 비즈니스 로직을 포함하는 경우: 프로퍼티에 비즈니스 로직이 포함되어있으면, 객체가 어떻게 동작할지 예측하기 어렵습니다.
  • 결정적이지 않은 경우: get의 멱등성이 깨질 수 있습니다. 함수를 여러번 호출하게되면, 반환값이 달라질 수 있습니다.
  • 변환의 경우: 타입 변환의 경우에도, Int.toDouble() 같은 함수를 사용하지, 프로퍼티에서 변환을 처리할거라 예상하기 어렵습니다.
  • 연산 비용이 높거나, 복잡도가 O(1)보다 큰 경우: 보통 프로퍼티getter, setter가 연산비용이 높다고 생각하지 않습니다. 이는 개발자의 최적화 대상에서 빠지게 됩니다.
  • 게터에서 프로퍼티의 상태 변경이 일어나야 하는 경우: 관습적으로 getter, setter가 객체의 상태를 변화시킬거라고 생각하지 않습니다. 따라서 게터에서 프로퍼티의 상태 변화를 일으킨다면, 함수를 사용하는 것이 좋습니다.

 

많은 사람은 경험적으로, 프로퍼티는 상태 집합을 나타내고, 함수는 행동을 나타낸다고 생각합니다.

반응형