안드로이드 개발자 노트
[이펙티브 코틀린] Item21. 일반적인 프로퍼티 패턴은 프로퍼티 위임으로 만들어라 본문
코틀린은 코드 재사용과 관련해서 프로퍼티 위임이라는 새로운 기능을 제공합니다.
프로퍼티 위임을 사용하면 일반적인 프로퍼티의 행위를 추출해서 재사용할 수 있습니다.
대표적인 예로 지연 프로퍼티가 있으며, 코틀린의 stdlib는 lazy 프로퍼티 패턴을 쉽게 구현할 수 있게 lazy 함수를 제공합니다.
val value by lazy { createValue() }
프로퍼티 위임을 사용하면, 변화가 있을 때 이를 감지하는 observable 패턴을 쉽게 만들 수 있습니다.
val items: List<Item> by
Delegates.observable(listOf()) { _, _, _ ->
notifyDataSetChanged()
}
val key: String? by
Delegates.observable(null) { _, old, new ->
Log.e("key changed from $old to $new")
}
프로퍼티 위임 메커니즘을 활용하면, 다양한 패턴(리소스 바인딩, 의존성 주입, 데이터 바인딩) 들을 만들 수 있습니다.
이런 패턴들을 사용할 때 자바 등에서는 어노테이션을 많이 활용해야하지만 코틀린은 프로퍼티 위임을 사용해서 간단하고 type-safe하게 구현할 수 있습니다.
// 안드로이드에서의 뷰와 리소스 바인딩
private val button: Button by bindView(R.id.button)
private val textSize by bindDimension(R.dimen.font_size)
private val doctor: Doctor by argExtra(DOCTOR_ARG)
// kotlin 에서의 종속성 주입
private val presenter: MainPresenter by inject()
private val repository: NetworkRepository by inject()
private val vm: MainViewModel by viewModel()
// 데이터 바인딩
private val port by bindConfiguration("port")
private val token: String by preferences.bind(TOKEN_KEY)
다음은 프로퍼티 위임을 활용한 일부 프로퍼티가 사용될 때, 간단한 로그를 출력하는 예시입니다.
var token: String? = null
get() {
print("token returned value $field")
return field
}
set(value) {
print("token changed from $field to $value")
field = value
}
var attempts: Int = 0
get() {
print("attempts returned value $field")
return field
}
set(value) {
print("attempts changed from $field to $value")
field = value
}
프로퍼티 위임은 다른 객체의 메서드를 활용해서 프로퍼티 접근자(게터와 세터)를 만드는 방식입니다.
게터는 getValue, 세터는 setValue 함수를 사용해서 만들어야 하며, 객체를 만든 뒤에는 by 키워드를 사용해서 연결해 주면 됩니다.
var token: String? by LoggingProperty(null)
var attempts: Int by LoggingProperty(0)
private class LoggingProperty<T>(var value: T) {
operator fun getValue(
thisRef: Any?,
prop: KProperty<*>
): T {
print("${prop.name} returned value $value")
return value
}
operator fun setValue(
thisRef: Any?,
prop: KPRoperty<*>,
newValue: T
) {
val name = prop.name
print("name changed from $value to $newValue")
value = newValue
}
}
프로퍼티 위임이 어떻게 동작하는지 이해하려면, by가 어떻게 컴파일되는지 보는 것이 좋습니다.
JvmField
private val 'token$delegate' = LoggingProperty<String>(null)
var token: String?
get() = 'token$delegate'.getValue(this, ::token)
set(value) {
'token$delegate'.setValue(this, ::token, value)
}
etValue와 setValue는 단순하게 값만 처리하도록 바뀌는 것이 아니라, 컨텍스트(this)와 프로퍼티 레퍼런스의 경계도 함께 사용하는 형태로 바뀝니다.
프로퍼티에 대한 레퍼런스는 이름, 어노테이션과 관련된 정보 등을 얻을 때 사용됩니다.
그리고 컨텍스트는 함수가 어떤 위치에서 사용되는지와 관련된 정보를 제공해줍니다.
이러한 정보로 인해서 getValue와 setValue메서드가 여러 개 있어도 문제가 없습니다.
getValue와 setValue 메서드가 여러개 있어도 컨텍스트를 활용하므로, 상황에 따라서 적절한 메서드가 선택됩니다.
이는 다양하게 활용되는데 여러 종류의 뷰와 함께 사용될 수 있는 델리게이트가 필요한 경우가 있습니다.
이는 다음과 같이 구현해서, 컨텍스트의 종류에 따라서 적절한 메서드가 선택되게 만들 수 있습니다.
class SwipeRefreshBinderDelegate(val id: Int) {
private var cache: SwipeRefreshLayout? = null
operator fun getValue(
activity: Activity,
prop: KProperty<*>,
): SwipeRefreshLayout {
return cache?: activity
.findViewById<SwipeRefreshLayout>(id)
.also { cache = it }
}
operator fun getValue(
fragment: Fragment,
prop: KProperty<*>
): SwipeRefreshLayout {
return cache?: fragment.view
.findViewById<SwipeRefreshLayout>(id)
.also { cache = it }
}
}
객체를 프로퍼티 위임하려면 val의 경우 getValue연산, var의 경우 getValue와 setValue 연산이 필요합니다.
이러한 연산은 확장 함수로도 만들 수 있습니다.
다음 코드는 Map<String, *>를 사용하는 예입니다.
val map: Map<String, Any> = mapOf(
"name" to "Marcin",
"kotlinProgrammer" to true
)
val name by map
print(name) // Marcin
이는 코틀린 stdlib에 다음과 같은 확장 함수가 정의되어 있어서 사용할 수 있는 것입니다.
inline operator fun <V, V1 : V> Map<in String, V>
.getValue(thisRef: Any?, property: KProperty<*>): V1 =
getOrImplicitDefault(property.name) as V1
코틀린 stdlib에서 다음과 같은 프로퍼티 델리게이터를 알아 두면 좋습니다.
- lazy
- Delegates.observable
- Delegates.vetoable
- Delegates.notNull
정리
- 프로퍼티 델리게이트는 프로퍼티와 관련된 다양한 조작을 할 수 있으며, 컨텍스트와 관련된 대부분의 정보를 갖는데 이러한 특징으로 인해서 다양한 프로퍼티의 동작을 추출해서 재사용할 수 있다.
- 프로퍼티 위임은 프로퍼티 패턴을 추출하는 일반적인 방법이라 많이 사용되고 있다.
'Kotlin > 이펙티브 코틀린' 카테고리의 다른 글
[이펙티브 코틀린] Item22. 일반적인 알고리즘을 구현할 때 제네릭을 사용하라 (0) | 2023.11.11 |
---|---|
[이펙티브 코틀린] Item20. 일반적인 알고리즘을 반복해서 구현하지 말라 (0) | 2023.11.05 |
[이펙티브 코틀린] Item19. knowledge를 반복하여 사용하지 말라 (0) | 2023.11.05 |