일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 안드로이드 라이선스
- 안드로이드 유닛 테스트 예시
- 서비스 쓰레드 차이
- 안드로이드 라이선스 종류
- 멤버변수
- Rxjava Observable
- 안드로이드 os 구조
- 2022 플러터 안드로이드 스튜디오
- 큐 자바 코드
- android ar 개발
- 안드로이드 유닛 테스트
- 서비스 vs 쓰레드
- 자바 다형성
- 2022 플러터 설치
- 스택 큐 차이
- jvm 작동 원리
- 안드로이드 유닛테스트란
- android retrofit login
- ar vr 차이
- ANR이란
- jvm이란
- 안드로이드 레트로핏 crud
- 스택 자바 코드
- 안드로이드 레트로핏 사용법
- rxjava cold observable
- 플러터 설치 2022
- 클래스
- 객체
- rxjava hot observable
- rxjava disposable
- Today
- Total
나만을 위한 블로그
[이펙티브 코틀린] 아이템 21. 일반적인 프로퍼티 패턴은 프로퍼티 위임으로 만들어라 본문
코틀린은 코드 재사용과 관련해서 프로퍼티 위임이라는 기능을 제공한다. 프로퍼티 위임을 쓰면 일반적인 프로퍼티의 행위를 추출해 재사용할 수 있다. 대표적인 예로 지연 프로퍼티가 있다. lazy 프로퍼티는 이후에 처음 사용하는 요청이 들어올 때 초기화되는 프로퍼티를 의미한다.
일반적으로 대부분의 언어(자바스크립트 등)에서는 필요할 때마다 복잡하게 구현해야 하지만 코틀린에선 프로퍼티 위임을 활용해 간단하게 구현할 수 있다. 코틀린의 stdlib는 lazy 프로퍼티 패턴을 쉽게 구현할 수 있게 lazy 함수를 제공한다.
val value by lazy { createValue() }
프로퍼티 위임을 쓰면 이외에도 변화가 있을 때 이를 감지하는 observable 패턴을 쉽게 만들 수 있다. 목록을 출력하는 리스트 어댑터가 있다면 변경 내용을 출력해야 할 경우 stdlib의 observable 델리게이트를 기반으로 간단하게 구현할 수 있다.
import android.util.Log
import kotlin.properties.Delegates
val items: List<Item> by Delegates.observable(listOf()) { _, _, _ ->
notifyDataSetChanged()
}
val key: String? by Delegates.observable(null) { _, old, new ->
Log.e("TAG", "key changed from $old to $new")
}
일반적으로 프로퍼티 위임 매커니즘을 활용하면 다양한 패턴들을 만들 수 있다. 뷰, 리소스 바인딩, 의존성 주입, 데이터 바인딩 등이 있다. 일반적으로 이런 패턴들을 사용할 때 자바 등에선 어노테이션을 많이 활용해야 한다. 하지만 코틀린은 프로퍼티 위임을 써서 간단하고 type-safe하게 구현할 수 있다.
어떻게 이런 코드가 가능하고 프로퍼티 위임을 어떻게 활용할 수 있는지 보기 위해 간단한 프로퍼티 델리게이트를 만든다.
예를 들어 일부 프로퍼티가 사용될 때 간단한 로그를 출력한다고 한다. 아래는 게터, 세터에서 로그를 출력하는 기본적인 구현 방법이다.
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 키워드를 써서 getValue, setValue를 정의한 클래스와 연결하면 된다.
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가 어떻게 컴파일되는지 보는 게 좋다. getValue, setValue는 단순하게 값만 처리하게 바뀌는 게 아니라 컨텍스트(this)와 프로퍼티 레퍼런스의 경계도 함께 쓰는 형태로 바뀐다. 프로퍼티에 대한 레퍼런스는 이름, 어노테이션 관련 정보 등을 얻을 때 사용된다. 컨텍스트는 함수가 어떤 위치에서 사용되는지와 관련된 정보를 제공한다.
이런 정보로 인해 getValue, setValue가 여러 개 있어도 문제없다. 컨텍스트를 활용하므로 상황에 따라 적절한 메서드가 선택된다.
객체를 프로퍼티 위임하려면 val의 경우 getValue 연산, var의 경우 getValue, setValue 연산이 필요하다. 이런 연산은 확장 함수로도 만들 수 있다. 아래는 Map<String, *>를 쓰는 예시다.
val map: Map<String, Any> = mapOf(
"name" to "Marcin",
"kotlinProgrammer" to true
)
fun main() {
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
굉장히 범용적으로 쓰이는 패턴들에 대한 프로퍼티 델리게이터이므로 알아두면 좋다. 또한 프로퍼티를 직접 만들어 쓸 수 있다는 것도 기억하라.
'책 > Effective Kotlin' 카테고리의 다른 글
[이펙티브 코틀린] 아이템 23. 타입 파라미터의 섀도잉을 피하라 (0) | 2022.09.13 |
---|---|
[이펙티브 코틀린] 아이템 22. 일반적인 알고리즘 구현 시 제네릭을 써라 (0) | 2022.09.12 |
[이펙티브 코틀린] 아이템 20. 일반적인 알고리즘을 반복해서 구현하지 마라 (0) | 2022.09.10 |
[이펙티브 코틀린] 아이템 19. knowledge를 반복해 사용하지 마라 (0) | 2022.08.30 |
[이펙티브 코틀린] 아이템 18. 코딩 컨벤션을 지켜라 (0) | 2022.08.07 |