Property Delegate란?
코틀린의 특성 중 하나로 Property Delegate를 사용하면 Property의 접근자(getter, setter) 구현을 다른 객체에게 맡김으로써 기존 객체가 직접 작업을 수행하지 않고 객체를 위임받은 또 다른 객체가 해당 작업을 처리하게 된다. 이렇게 되면 반복적으로 사용되는 Property 행위를 추출해서 재사용함으로써 코드를 간결하게 만들 수 있다.
이를 구현하기 위해서는 객체에서 "by" 키워드를 이용해서 위임해주어야 한다.
class Person {
var name: String by PersonDelegate("null")
val age: String by PersonDelegate("24")
}
class PersonDelegate(var value: String) {
operator fun getValue(person: Person, property: KProperty<*>): String {
println("${property.name} getValue: ${this.value}")
return value
}
operator fun setValue(person: Person, property: KProperty<*>, newValue: String) {
println("${property.name} setValue: ${this.value} -> $newValue")
this.value = newValue
}
}
fun main() {
val person = Person()
println("Person Name: ${person.name}\tPerson Age: ${person.age}") // -> getValue 호출
println()
person.name = "Hong GilDong" // -> setValue 호출
println()
println("Person Name: ${person.name}\tPerson Age: ${person.age}") // -> getValue 호출
println()
}
결과
name getValue: null
age getValue: 24
Person Name: null Person Age: 24
name setValue: null -> Hong GilDong
name getValue: Hong GilDong
age getValue: 24
Person Name: Hong GilDong Person Age: 24
위와 같이 기본적으로 Property Delegate를 구현해 보았다.
Person의 Property를 PropertyDelegate 객체에 각각 위임해 주었다. 이때 Person에 있는 property가 val이라면 getter만 가능하고 var 이라면 getter와 setter 모두 구현 가능하다.
다음과 같이 DB에 데이터를 추가하고 리턴하는 코드도 Property Delegate를 이용해서 구현 가능하다.
data class Customer(val id: Int = -1, val name: String = "", var address: String = "", var phone: String = "")
val dataBase = mutableSetOf(
Customer(1, "가나다", "서울시 마포구", "010-1234-5678"),
Customer(2, "라마바", "부산시 강남구", "010-5678-1234"),
Customer(3, "사아자", "전주시 광진구", "010-8765-4321"),
Customer(4, "차카타", "울산시 금천구", "010-4321-8765")
)
// getValue에 대응하는 함수. 주어진 id에 해당하는 고객 정보를 'dataBase'에서 찾아 리턴
fun queryForCustomer(id: Int) = dataBase.find { customer ->
customer.id == id
} ?: Customer(-1, "자료없음", "자료없음", "자료없음")
// setValue에 대응하는 함수. 주어진 Customer 객체를 dataBase에 추가
fun updateForCustomer(customer: Customer) = dataBase.add(customer)
class Person {
var id: Int = -1
var customerInfo: Customer by PersonDelegate()
}
// Property 위임을 위한 class. Person의 customerInfo property를 위임
class PersonDelegate {
// 실제 속성(CustomerInfo)을 호출하게 되면 StackOverFlow 발생
operator fun getValue(person: Person, property: KProperty<*>): Customer {
return queryForCustomer(person.id)
}
/*
실제 위임받은 속성(customerInfo)에 값을 세팅할 수는 없다
reflect를 사용하기 때문에
*/
operator fun setValue(peron: Person, property: KProperty<*>, value: Customer) {
if (updateForCustomer(value)) {
println("정상 입력!")
} else {
println("입력 실패!")
}
}
}
fun main() {
val person = Person()
person.customerInfo = Customer(5, "하하하", "서울시 종로구", "010-1029-3847") // -> setValue 호출
person.id = 3
println(person.customerInfo) // -> getValue 호출
}
결과
정상 입력!
Customer(id=3, name=사아자, address=전주시 광진구, phone=010-8765-4321)