import java.lang.reflect.Type
class Delegate(){
operator fun getValue(/*...*/) { /*...*/ } // getter를 구현하는 로직
operator fun setValue(/*...*/, value: Type) { /*...*/ } // setter를 구현하는 로직
operator fun provideDelegate(/*...*/): Delegate { /*...*/ } // 위임 객체 생성, 제공
}
class Foo {
var p : Type by Delegate() // by 키워드 : 프로퍼티와 위임 객체를 연결
}
fun main() {
val foo = Foo() // provideDelegate 호출
val oldValue = foo.p // delegate.getValue 호출
foo.p = newValue // delegate.setValue 호출
}
class Email { /*...*/ }
fun loadEmails(person: Person): List<Email> {
println("${person.name}의 이메일을 가져옴")
return listOf(/*...*/)
}
class Person(val name: String) {
private var _emails: List<Email>? = null // 데이터를 저장하고 emails의 위임 객체 역할을 하는 프로퍼티
val emails: List<Email>
get() {
if(_emails == null) {
_emails = loadEmails(this) // 최초 접근시 이메일을 가져옴
}
return _emails!! // 저장해놓은 데이터가 있으면 그 데이터를 반환
}
}
fun main() {
val p = Person("Alice")
p.emails // 최초로 emails에 접근할 때만 이메일을 가져옴
// Alice의 이메일을 가져옴
p.emails
}
뒷바침하는 프로퍼티를 사용
_emails → 값을 저장, null이 될 수 있는 타입
emails → _emails에 대한 읽기 연산을 제공, null이 될 수 없는 타입
뒷바침하는 프로퍼티의 이름은 관례를 따름
뒷받침하는 프로퍼티 사용의 단점
class Person(val name: String) {
val emails by lazy { loadEmails(this) }
}
fun interface Observer {
fun onChange(name: String, oldValue: Any?, newValue: Any?)
}
open class Observable {
val observers = mutableList<Observer>()
fun notifyObservers(propName: String, oldValue: Any?, newValue: Any?) {
for(obs in observers){
obs.onChange(propName, oldValue, newValue)
}
}
}
class Person(val name: String, age: Int, salary: Int): Observable() {
var age: Int = age
set(newValue){
val oldValue = field // 뒷받침하는 프로퍼티에 접근할 때 field 식별자를 사용
field = newValue
notifyObservers("age", oldValue, newValue)
}
var salary: Int = salary
set(newValue){
val oldValue = field
field = newValue
notifyObservers("salary", oldValue, newValue)
}
}
fun main() {
val p = Person("seb", 28, 1000)
p.observers += Observer {propName, OldValue, newValue ->
// 함수형 인터페이스에 대한 간편한 구문을 사용해
// 옵저버를 생성하고 이를 등록하여 프로퍼티의 변경을 기다림
println(
"""
Property $propName changed from $oldValue to $newValue!
""".trimIndent()
)
}
p.age = 29
// Property age changed from 28 to 29
p.salary = 1500
// Property salary changed from 1000 to 1500
}
class ObservableProperty(
val propName: String,
var propValue: Int,
val observable: Observable
) {
fun getValue(): Int = propValue
fun setValue(newValue: Int) {
val oldValue = propName
propValue = newValue
observable.notyiObservers(propName, oldValue, newValue)
}
}
class Person(val name: String, age: Int, salary: Int): Observable() {
val _age = observableProperty("age", age, this)
var age: Int
get() = _age.getValue()
set(newValue) {
_age.setValue(newValue)
}
val _salary = ObservableProperty("salary", salary, this)
var salary: Int
get() = _salary.getValue()
set(newValue) {
_salary.setValue(newValue)
}
}
import kotiln.reflect.KProperty
class ObservableProperty(var propValue: Int, val observable: Obvservable) {
operator fun getValue(thisRef: Any?, prop: KProperty<*>): Int = propValue
operator fun setValue(thisRef: Any?, prop: KProperty<*>, newValue: Int) {
val oldValue = propValue
propValue = newValue
observable.notifyObservers(prop.name, oldValue, newValue)
}
}
class Person(val name: String, age: Int, salary: Int): Observable() {
var age by ObservableProperty(age, this)
var salary by ObservableProperty(salary, this)
}
import kotiln..properties.Delegates
class Person(val name: String, age: Int, salary: Int): Observable() {
private val onChange = {
property: KProperty<*>,
oldValue: Any?,
newValue: Any? -> notifyObservers(property.name, oldValue, newValue)
}
var age by Delegates.observable(age, onChange)
var salary by Delegates.observable(salary, onChange)
}
프로퍼티 위임은 완전히 제네릭하여 모든 타입에 사용할 수 있음
위임 프로퍼티 클래스 예시
class C {
var prop: Type by MyDelegate()
}
val c = C()
컴파일러는 다음의 코드를 생성
class C {
private val <delegate> = MyDelegate()
var prop: Type
get() = <delegate>.getValue(this, <property>)
set(value: Type) = <delegate>.setValue(this, <property>, value)
}

자신의 프로퍼티를 동적으로 정의할 수 있는 객체를 만들 때 위임 ㅍ로퍼티를 활용하는 경우가 자주 있음
class Person {
private val _attributes = mutableMap<String, String>()
fun setAttributes(attrName: String, value: String) {
_attributes[attrName] = value
}
var name: String
get() = _attributes["name"]!!
set(value) {
_attributes["name"] = value
}
}
fun main() {
val p = Person()
val data = mapOf("name" to "Seb", "company" to "JetBrains")
for((attrName, value) in data)
p.setAttribute(attrName, value)
println(p.name)
// Seb
p.name = "Sebastian"
println(p.name)
// Sebastian
}
By 키워드 뒤에 맵을 직접 넣음
class Person {
private val _attributes = mutableMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attribute[attrName] = value
}
var name: String by _attributes // 위임 프로퍼티로 맵 사용
}
object Users: IdTable() { // 데이터베이스 테이블
val name = varchar("name", length = 50).index() // 테이블 칼럼
val age = integer("age")
}
class User(id: EntityID): Entity(id) { // 각 테이블에 들어있는 구체적인 엔티티
var name: String by Users.name // 데잍베이스에 저장된 사용자의 이름 값
var age: Int by Users.age
}
object Users: IdTable() {
val name: Column<String> = varchar("name", 50).index()
val age: Column<Int> = integer("age")
}
코틀린 위임 객체에 관례에 따른 시그니처 요구 사항을 충족
operator fun <T> Column<T>.getValue(o: Entity, desc: KProperty<*>): T {
// 데이터베이스에서 칼럼 값 가져오기
}
operator fun <T> Column<T>.setValue(o: Entity, desc: KProperty<*>, value: T) {
// 데이터베이스의 값 변경하기
}