오늘도 더 나은 코드를 작성하였습니까?

연산자 오버로딩과 기타 관례(위임 프로퍼티 기타) 본문

Kotlin in Action/코틀린 답게 사용하기

연산자 오버로딩과 기타 관례(위임 프로퍼티 기타)

hik14 2020. 8. 25. 19:38

위임 프로퍼티 컴파일 규칙

class C {
    var prop: Type by MyDelegate()
}

val c = C()

컴파일러는 MyDelegate 클래스의 인스턴스를 감춰진 프로퍼티에 저장하며 그 감춰진 프로퍼티를 <delegate>라는 이름으로 부른다.

프로퍼티를 표현하기 위해 KProperty 타입의 객체를 사용한다. 이 객체를 <property>라고 부른다.

 

class C {
    private val <delegate> = MyDelegate()
    
    var prop: Type 
    get() = <delegate>.getValue(this, <property>)
    set(value: Type) = <delegate>.setValue(this, <property>, value)
}

val x  = c.prop  -------> val x = <delegate>. getValue(c, <property>)

 

c.prop = x  -------> <delegate>. setValue( c, <property>,  x )

 

이 메커니즘은 상당히 단순하지만 흥미로운 활용법이 많다.

프로퍼티 값이 저장될 장소를 바꿀 수도 있고(맵, 데이터베이스, 사용자 세션, 쿠키)

프로퍼티를 읽거나 쓸때 벌어질 일을 변경할 수도 있다.(값 검증, 변경 통지)

 

프로퍼티 값을 맵에 저장하기

자신의 프로퍼티를 동적으로 정의할 수 있는 객체를 만들 때 위임 프로퍼티를 활용하는 경우가 자주 있다.

예를 들면 연락처 별로 임의 정보를 저장할 때 필수적인 정보가 있을 거고 사람마다 달라질 수 있는 추가 정보가 있다.

class Person {

    private val _attributes = hashMapOf<String, String>()

    fun setAttribute(attrName: String, value: String){
        _attributes[attrName] = value
    }

    val name: String by _attributes
    val phone: String by _attributes
}

fun main() {

    val map = mapOf("name" to "hongilkwon", "phone" to "01011112222")
    val person = Person()

    for( (prop, value) in map)
        person.setAttribute(prop, value)

    println(person.name)
    println(person.phone)
}

 

위와 같은 코드를 by를 이용하여 아주 쉽게 위임 프로퍼티를 활용할 수 있다.

이것이 가능한 이유는 Map, MutableMap 인터페이스에 getValue setValue 확장 함수가 만들어져 있기 때문이다.

class Person {
    private val _attributes = hashMapOf<String, String>()
    fun setAttribute(attrName: String, value: String){
        _attributes[attrName] = value
    }

    val name: String by _attributes
}

p.name  ----> _attributes.getValue(p, prop)

 

프레임워크에서 위임 프로퍼티 사용하기

객체 프로퍼티를 저장하거나 변경하는 방법을 바꿀 수 있으면 프레임 워크를 개발할 때 유용하다.

 

데이터베이스에 User라는 테이블이 있고 name age를 컬럼으로 갖는다.

object Users: IdTable() { // 데이터 베이스 테이블에 해당한다. 

    val name = varchar("name", length = 50).index() // name칼럼
    val age = integer("age") // age 칼럼

}

class User(id: EntityID) : Entity(id){ // 각 인스턴스는 User 엔티티
    var name: String by Users.name
    var age: Int by Users.age
}

 

Users 객체는 데이터베이스 테이블이라 1개만 존재 해야되서 싱글턴으로 생성.

 

User의 상위 클래스인 Entity클래스는 데이터베이스 칼럼을 엔티티의 속성 값으로 연결해주는 매핑이 있다.

 

User프로퍼티에 접근할때 자동으로 Entity클래스에 정의된 데이터베이스 맵핑으로 필요한 값을 가져온다.

 

Users.name (varchar)  User.age(integer)는 각각 위임 객체 관례에 따른 시그니처를 요구사항을 구현한다.

operator fun <T> Column<T>.getValue(o: Entity, desc: KProperty<*>): T{
    
}

operator fun <T> Column<T>.setValue(o: Entity, desc: KProperty<*>, value: T): T{

}