본문 바로가기
프로그래밍 놀이터/Kotlin, Coroutine

[Kotlin Tutorial] Operator overload 와 convention #2

by 돼지왕 왕돼지 2017. 8. 29.
반응형

 [Kotlin Tutorial] Operator overload 와 convention #2


출처 : Kotlin in action


Access, array, array component, attribute set, backing field, backing property technique, by, by delegate, by lazy(), collection, column, compareTo, Compiler, component convention, component function, component function 자동 생성, componentN, contains, convention, data class, delegate class, delegated properties, delegated properties in framework, delegated-property translation rule, Delegates.observable, delegation, destructing declaration, Destructuring, destructuring declaration, Destructuring declarations and loops, equals, expando objects, expression, function call, Get, getvalue, hidden property, Implementing delegated properties, in, In operation, initialize, iterating, Java, kproperty, LAMBDA, lambda param, Lazy, lazy function, lazy init, lazy initialization, LOCK, map, map iteration, mapping, MODIFY, mutablemap, namecomponents, observableproperty, operation overload, operator, option, Pair, primary constructor, property, property change observer, property value, PropertyChangeEvent, PropertyChangeSupport, RANGE, Reusing property accessor logic: delegated properties, Set, setValue, standard lib, Store, storing property values in a map, thread-safe, thread-safe option, Triple, VARCHAR, [Kotlin Tutorial] Operator overload 와 convention #2, [], 동적 attribute set


7.3. Destructuring declarations and component functions


-

val p = Point(10, 20)

val (x, y) = p // destructuring

println(x)

println(y)



-

destructuring 도 convention 을 사용한다.

componentN 이 호출된다. ( N 은 숫자 )

val (a, b) = p 

// val a = p.component1()

// val b = p.component2()



-

data class 는 compiler 가 primaryConstructor 에 정의된 모든 property 에 대해 componentN 함수를 자동 생성한다.

// data class 였다면 아래 component 함수들은 자동 생성

class Point(val x: Int, val y: Int){

    operator fun component1() = x

    operator fun component2() = y

}



-

destructuring declaration 이 유용한 경우는 한 개의 function에서 여러개의 value 를 return 하고자 할 때이다.

data class NameComponents(val name:String, val extension: String)


fun splitFilename(fullName: String): NameComponents{ 

    val (name, extension) = fullName.split(‘.’, limit = 2) // componentN 은 array 와 collection 에도 정의되어 있다.

    return NameComponents(name, extension)

}


val (name, ext) = splitFilename(“example.kt”)

println(name)

println(ext)


여러 개의 value 를 return 할 때 Pair 와 Triple 을 사용하는 것이 더 간단하고 좋은 경우도 많다.




7.4.1. Destructuring declarations and loops


-

destructuring 이 가장 유용하게 사용되는 사례 중 하나는 map iteration 이다.

fun printEntries(map: Map<String, String>){

    for((key, value) in map){

        println(“$key -> $value”)

    }

}


map 에 iterator 가 구현되어 있다는 것과,

Entry 에 component1 과 component2 가 구현되어 있다는 것을 알 수 있다.





7.5. Reusing property accessor logic: delegated properties


-

delegated properties 는 backing field 에 값을 저장하는 것이 아닌 조금 더 복잡한 로직을 

모든 accessor 를 override 하지 않고 구현할 수 있다.

예를 들면 property 는 value 를 database table 에 저장한다던지, browser session 에 저장한다던지, map 에 저장한다던지 등등..


이것의 기본 원리는 delegation 이다.




7.5.1. Delegated properties: the basics


-

아래가 delegated property 의 기본 syntax 이다.

class Foo{

    var p: Type by Delegate()

}


p 의 accessor 의 기능을 Delegate class 에 위임한다.

아래와 같은 코드가 생성된다고 보면 된다.

class Foo{

    private val delegate = Delegate()

    var p: Type

        set(value:Type) = delegate.setValue(…, value)

        get() = delegate.getValue(…)

}



-

Delegate class 는 convention 으로써 getValue 와 setValue(read-only 가 아닐 때만) 가 있어야 한다.

member 함수로 있어도 되고 extension 이어도 된다.

class Delegate{

    operator fun getValue(…){ … }

    operator fun setValue(…, value:Type){ …}

}




7.5.2. Using delegated properties: lazy initialization and “by lazy()”


-

class Person(val name:String){

    private var _emails: List<Email>? = null


    val emails: List<Email>

        get(){

            if(_emails == null){

                _emails = loadEmails(this)

            }

            return _emails!!

        }

}


_emails 와 emails 를 분리시킨 것을 backing property technique 이라고 부른다.

lazy initialization 에 주로 쓰이는 방법이다.

좋은 방법이긴 하지만 이는 thread-safe 하지 않고, lazy init 해야 하는 것들이 많으면 코드 복잡성이 높아진다.



-

Kotlin 이 이런 것들을 그냥 내둘리가 없다.

lazy 라는 standrad lib function 으로 해결 할 수 있다.

class Person(val name:String){

    val emails by lazy{ loadEmails(this) }

}


lazy 는 lambda 를 param 으로 받는다.

lazy 는 기본적으로 thread-safe 하다. 어떤 lock 을 쓸지 option 을 줄 수 있고, thread-safe 옵션을 끌 수도 있다.






7.5.3. Implementing delegated properties


-

어떤 property 가 변경되었음을 알기 위해서 Java 에서는 PropertyChangeSupport 와 PropertyChangeEvent class 를 사용했어야 한다.

open class PropertyChangeAware{

    protected val changeSupport = PropertyChangeSupport(this)


    fun addPropertyChangeListener(listener: PropertyChangeListener){

        changeSupport.addPropertyChangeListener(listener)

    }


    fun removePropertyChangeListener(listener: PropertyChangeListener){

        changeSupport.removePropertyChangeListener(listener)

    }

}


class Person(val name:String, age: Int, salary: Int) : PropertyChangeAware(){

    var age: Int = age

        set(newValue){

            val oldValue = field

            field = newValue

            changeSupport.firePropertyChange(“age”, oldValue, newValue)

        }


    var salary: Int = salary

        set(newValue){

            val oldValue = field

            field = newValue

            changeSupport.firePropertyChange(“salary”, oldValue, newValue)

        }

}


코드를 refactoring 해보자.

class ObservableProperty(val propName:String, var propValue: Int, val changeSupport: PropertyChangeSupport){

    fun getValue(): Int = propValue

    fun setValue(newValue: Int){

        val oldValue = propValue

        propValue = newValue

        changeSupport.firePropertyChange(propName, oldValue, newValue)

    }

}


class Person(val name:String, age:Int, salary:Int):PropertyChangeAware(){

    val _age = ObservableProperty(“age”, age, changeSupport)

    var age:Int

        get() = _age.getValue()

        set(value){ _age.setValue(value) }


    val _salary = ObservableProperty(“salary”, salary, changeSupport)

    var salary: Int

        get() = _salary.getValue()

        set(value){ _salary.setValue(value) }

}


또 한번 refactoring 해보자.

class ObservableProperty(var propValue: Int, val changeSupport: PropertyChangeSupport){

    operator fun getValue(p:Person, prop:KProperty<*>): Int = propValue // KProperty 에 대해서는 나중에 배운다

    operator fun setValue(p:Person, prop:KProperty<*>, newValue: Int){

        val oldValue = propValue

        propValue = newValue

        changeSupport.firePropertyChange(prop.name, oldValue, newValue)

    }

}


class Person{ val name:String, age:Int, salary: Int): PropertyChangeAware(){

    var age: Int by ObservableProperty(age, changeSupport) // 알아서 hidden property 를 만들어 처리해준당

    var salary: Int by ObservableProperty(salary, changeSupport)

}



-

여기서 끝이 아니겠지… ObservableProperty 를 직접 구현할 필요도 없다…

Kotlin 의 standard lib 에는 이미 ObservableProperty 비슷한 녀석이 구현되어 있다. 그러나 위의 예와는 다르게 PropertyChangeSupport 와 연결되어 있지는 않다.

class Person(val name:String, age:Int, salary:Int):PropertyChangeAware(){

    private val observer = {

        prop:kProperty<*>, oldValue:Int, newValue:Int -> changeSupport.firePropertyChange(prop.name, oldValue, newValue)

    }


    var age: Int by Delegates.observable(age, observer)

    var salary: Int by Delegates.observable(salary, observer)

}



-

by 의 오른쪽은 new instance creation 일 필요가 없다.

이 부분은 다른 function call, 다른 property 연결, 다른 expression 이 될 수 있다. ( 해당 값에 compiler 가 getValue, setValue 를 부를 수만 있다면 )




7.5.4. Delegated-property translation rules


-

class C{

    var prop: Type by MyDelegate()

}


val c = C()

위의 코드는 아래 코드를 생산한다고 보면 된다.


// <delegate> 와 <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 )




7.5.5. Storing property values in a map


-

delegated property 가 잘 사용되는 일반적인 경우는 동적으로 attribute set 이 결정되는 경우이다. ( nosql 처럼 생각하면 될듯 )

이것을 expando objects 라고 부른다.


class Person{

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

    fun setAttribute(attrName:String, value:String){

        _attributes[attrName] = value

    }


    val name:String

        get() = _attributes[“name”]!!


    // delegation 을 쓰면 간단해 질 수 있다.

    // val name:String by _attributes       

}

Map, MutableMap interface 에 getValue, setValue 를 extension function 으로 define 해놓았기 때문에 가능하다.




7.5.6. Delegated properties in frameworks


-

property 의 저장과 수정방법을 변경하는 것은 framework 개발자에게 아주아주 유용하다.

이것이 db framework 에 어떻게 쓰이는지는 나중에 배우겠다.

object Users: IdTable(){

    val name = varchar(“name”, length=50).index() // Column<String> type

    val age = integer(“age”) // Column<Int> type

}


class User(id:EntityID): Entity(id){

    var name: String by Users.name

    var age: Int by Users.age

}


Column class 는 framework 에서 getValue 와 setValue 를 정의해 놓았다.





7.6. Summary


-

Kotlin 은 몇몇의 mathematical operation 의 overload 를 지원한다.

이 overload 는 convention 이라고 해서 이름에 mapping 되며, 새로운 operator 를 정의할 수는 없다.



-

비교 operator 들은 equals 와 compareTo 에 매핑된다.



-

get, set, contains convention function 은 [], in operation 을 가능하게 한다.



-

range 를 만들고 collection 과 array 를 iterating 하는 것 역시 convention 을 통해 된다.



-

destructing declaration 은 한 개의 object 로부터 여러 개의 variable 을 한번에 assign 하는 것을 가능하게 한다.

이것은 한 function 에서 여러 개의 결과값을 return 할 때 유용하다.

data class 는 primary constructor 와 연결되어 자동으로 사용할 수 있고, componentN convention 을 사용해서 스스로 define 할 수도 있다.



-

delegated properties 는 property value 가 어떻게 store, initialiize, access, modify 되는지를 control 할 수 있다.

이것은 framework 를 만드는데 매우 유용한 툴이 될 수 있다.



-

lazy 함수는 property 를 lazy init 하는데 아주 큰 도움을 준다.



-

Delegates.observable 함수는 property change observer 를 달 수 있게 도와준다.



-

delegated properties 는 object 가 다양한 attribute set 을 가졌을 경우 map 과 함께 사용하는 것을 편하게 해준다.






반응형

댓글