일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- map
- function
- ML
- kotlin
- DART
- textstyle
- List
- package
- 다트
- animation
- variable
- crawler
- Yocto
- import
- set
- 콜렉션
- python
- text
- 함수
- 클래스
- 파이썬
- 크롤러
- Flutter
- 코틀린
- Collection
- Android
- 플러터
- 웹크롤러
- pushnamed
- Class
- Today
- Total
조용한 담장
코틀린(Kotlin) 클래스(Class) : 위임(Delegation) 본문
코틀린(kotlin) 클래스의 위임(delegation) 을 살펴보자.
원문 https://kotlinlang.org/docs/reference/delegation.html 을 보며 정리.
Implementation by Delegation
kotlin 은 상속의 대안이 되는 Delegation pattern 을 boilerplate code 없이 지원한다.
아래 예제에서, Derived 클래스는 자신의 모든 public 멤버들을 특정 오브젝트(BaseImpl)로 위임하여 Base 인터페이스를 구현할 수 있다.
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print()
}
// output:
// 10
Derived 클래스의 supertype list 안에 쓰인 by 절 은 b 는 Derived 의 오브젝트들 안에 내부적으로 저장될 것이고 컴파일러는 b 로 보내는 Base 의 모든 메소드들을 생성할 것임을 나타낸다.
Overriding a member of an interface implemented by delegation
컴파일러는 위임 오브젝트의 것들 대신 override 된 것들을 사용하게 된다.
Derived 에 override fun printMessage() { print("abc") } 를 추가한다면 printMessage 를 호출 했을 때 "10" 대신 "abc" 를 출력하게 될것이다.
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
override fun printMessage() { print("abc") }
}
fun main() {
val b = BaseImpl(10)
Derived(b).printMessage()
Derived(b).printMessageLine()
}
// output:
// abc10
하지만 이렇게 override 된 멤버들은 위임 오브젝트의 멤버로부터 호출 되지 않는다.
위임 오브젝트의 멤버들은 인터페이스 멤버들의 구현들만 접근이 가능하다.
interface Base {
val message: String
fun print()
}
class BaseImpl(val x: Int) : Base {
override val message = "BaseImpl: x = $x"
override fun print() { println(message) }
}
class Derived(b: Base) : Base by b {
// This property is not accessed from b's implementation of `print`
override val message = "Message of Derived"
}
fun main() {
val b = BaseImpl(10)
val derived = Derived(b)
derived.print()
println(derived.message)
}
// output:
// BaseImpl: x = 10
// Message of Derived
Delegated Properties
class Example {
var p: String by Delegate()
}
위임 프로퍼티의 문법 : val/var <property name>: <Type> by <expression>
프로퍼티의 get(), set() 은 getValue(), setValue() 로 위임된다.
프로퍼티 위임은 인터페이스를 갖지 않고 getValue() 와 setValue() 를 제공한다.
import kotlin.reflect.KProperty
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
프로퍼티 p 를 읽으면 Delegate 의 getValue() 가 호출되고, 값을 읽는 대상 오브젝트인 p 가 첫번째 파라미터가 되고 두번째 파라미터는 p 의 정보가 된다.
val e = Example()
println(e.p)
// outout:
// Example@33a17727, thank you for delegating ‘p’ to me!
p 로 값을 할당할 때는 setValue() 가 호출되고, 두개의 파라미터는 위와 같고 세번째 파라미터는 할당할 값이 된다.
e.p = "NEW"
// output:
// NEW has been assigned to ‘p’ in Example@33a17727.
함수나 코드블럭 안에 위임된 프로퍼티를 선언할 수 있기 때문에 반드시 클래스의 멤버가 될 필요는 없다.
Standard Delegates
kotlin standard library 가 제공하는 위임 메소드들 이다.
Lazy
lazy() 함수는 람다를 파라미터로 받고 lazy property 를 구현하기 위한 위임으로써의 기능을 제공하는 LAZY<T> 의 인스턴스를 리턴한다.
첫번째 get() 호출은 lazy() 로 전달된 람다를 실행하고 결과를 기억해두며, 후속으로 get() 이 호출되면 그 기억된 결과를 단순히 리턴 한다.
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main() {
println(lazyValue)
println(lazyValue)
}
// output:
// computed!
// Hello
// Hello
기본적으로 lazy properties 의 evaluation (값의 판단) 은 동기화 된다.
값은 한개의 쓰레드에서만 처리되며 모든 쓰레드는 같은 값을 보게 된다.
초기화 위임(initialization delegate)의 동기화가 필요하지 않으면 멀티 쓰레드는 동시에 실행할 수 있고 LazyThreadSafetyMode.PUBLICATION 를 lazy() 의 파라미터로 전달한다.
초기화는 항상 property 를 쓰는 한개의 쓰레드에서만 일어난다고 하면 LazyThreadSafetyMode.NONE 를 쓸 수 있다.
그러면 쓰레드 안전을 보장하며 관련된 오버헤드를 발생하지 않는다.
Observable
Delegates.observable() 는 초기값과 수정을 위한 핸들러를 인자로 받는다.
핸들러는 property 에 값을 할당할 때 마다 호출되며 값이 할당 될 property, 이전값과 새로운 값 세개의 파라미터를 갖는다.
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main() {
val user = User()
user.name = "first"
user.name = "second"
}
// output:
// <no name> -> first
// first -> second
할당 값을 중간에 가로채어 조건에 따라 할당 동작을 막고 싶다면 vetoable() 을 쓰면 된다.
핸들러는 새로운 property 값의 할당 전에 실행 된다.
Storing Properties in a Map
map 인스턴스 자신을 위임된 프로퍼티를 위한 위임으로 쓸 수 있다.
위임된 프로퍼티는 map 에서 값을 얻게된다.
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main() {
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
}
read-only Map 대신 MutableMap 을 쓰면 var 프로퍼티에도 쓸 수 있다.
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
Local Delegated Properties
지역 변수를 위임된 프로퍼티로 선언할 수 있다.
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
memoizedFoo 변수는 처음 사용될 때만 처리된다.
someCondition 이 false 이면 변수는 처리되지 않을 것이다.
Property Delegate Requirements
read-only property val 의 경우, 위임은 아래의 파라미터를 가지는 getValue 이름의 함수를 제공해야만 한다.
- thisRef : property owner 의 supertype 이거나 같아야 한다.
- property : KProperty<*> 타입 이거나 이것의 supertype 이어야 한다.
이 함수는 property 와 같은 타입이나 subtype 을 리턴해야 한다.
mutual property var 의 경우, 위임은 아래의 파라미터를 가지는 setValue 이름의 함수를 추가로 제공해야 한다.
- thisRef : getValue() 와 같다.
- property : getValue() 와 같다.
- new value : property 와 같은 type 이거나 subtype 이어야 한다.
getValue(), setValue() 함수는 위임 클래스나 확장 함수의 멤버 함수로 제공될 수 있다.
후자의 것은 이런 함수를 제공하지 않는 오브젝트에 프로퍼티를 위임할 필요가 있을 때 유용하다.
두 함수 모두 operator 키워드로 표시되어야 한다.
위임 클래스는 필요한 operator 메소드를 가진 ReadOnlyProperty 나 ReadWritePRoperty 인터페이스중 하나를 구현해야 한다.
이 인터페이스들은 kotlin standard library 에 선언되어 있다.
interface ReadOnlyProperty<in R, out T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
}
interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
Translation Rules
위임된 프로퍼티를 위해 코틀린 컴파일러는 먼저 보조 프로퍼티를 생성한 후 그것에 위임을 한다.
아래 예제 코드의 동작을 보면, prop 프로퍼티를 위해 prop$delegate 라는 숨겨진 프로퍼티가 생성되고 접근자들(get()/set())의 코드에서 단순히 이 추가된 프로퍼티로 위임을 한다.
class C {
var prop: Type by MyDelegate()
}
// this code is generated by the compiler instead:
class C {
private val prop$delegate = MyDelegate()
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
코틀린 컴파일러는 arguments 에 있는 prop 에 관한 모든 필요한 정보를 제공한다.
첫번째 argument this 는 클래스 C 의 인스턴스를 참조하고 this::prop 는 prop 자신을 묘사하는 KProperty 타입의 reflection object 이다.
코드에서 바로 bound callable reference 을 참조하기 위한 this::prop 문법은 코틀린 1.1 이후에 적용되어 있다.
Providing a delegate (since 1.1)
provideDelegate operator 를 정의 함으로써 오브젝트 생성의 로직을 구현이 위임된 프로퍼티로 확장할 수 있다.
오브젝트가 by 의 오른쪽에서 사용되면, provideDelegate 를 멤버나 확장 함수로써 정의 하고 그 함수는 프로퍼티 위임 인스턴스(property delegate instance) 를 생성하기 위해 호출된다.
사용의 예제로는 getter, setter 나 그외에서도 프로퍼티가 생성됬을때 프로퍼티의 일관성(consistency) 을 체크하는데 사용하는 경우가 있다.
바인딩(binding) 전에 프로퍼티의 이름을 체크하려면 아래의 예제처럼 할 수 있다.
class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}
class ResourceLoader<T>(id: ResourceID<T>) {
operator fun provideDelegate(
thisRef: MyUI,
prop: KProperty<*>
): ReadOnlyProperty<MyUI, T> {
checkProperty(thisRef, prop.name)
// create delegate
return ResourceDelegate()
}
private fun checkProperty(thisRef: MyUI, name: String) { ... }
}
class MyUI {
fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }
val image by bindResource(ResourceID.image_id)
val text by bindResource(ResourceID.text_id)
}
provideDelegate 의 파라미터들은 getValue 의 것과 같다.
- thisRef : property owner 의 supertype 이거나 같아야 한다.
- property : KProperty<*> 타입 이거나 이것의 supertype 이어야 한다.
provideDelegate 메소드는 MyUI 인스턴스를 생성할 때 각각의 프로퍼티에 대해 호출되고, 필요한 확인(validation) 이 바로 수행된다.
프로퍼티와 위임 사이의 바인딩을 가로채는 것 없이 같은 기능을 구현하려면 명확하기 프로퍼티의 이름을 전달해줘야 하며 이는 매우 불편한 방법이다.
// Checking the property name without "provideDelegate" functionality
class MyUI {
val image by bindResource(ResourceID.image_id, "image")
val text by bindResource(ResourceID.text_id, "text")
}
fun <T> MyUI.bindResource(
id: ResourceID<T>,
propertyName: String
): ReadOnlyProperty<MyUI, T> {
checkProperty(this, propertyName)
// create delegate
}
생성된 코드에서 provideDelegate 메소드는 보조 prop$delegate 프로퍼티 를 초기화 하기 위해 호출된다.
위의 예제의 생성된 코드와 프로퍼티 선언 val prop: Type by MyDelegate() 을 위해 새성된 코드를 비교해 보자.
class C {
var prop: Type by MyDelegate()
}
// this code is generated by the compiler
// when the 'provideDelegate' function is available:
class C {
// calling "provideDelegate" to create the additional "delegate" property
private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
provideDeleg
provideDelegate 메소드는 보조 프로퍼티의 생성에만 영향을 주며 getter, setter 의 생성된 코드에는 영향을 주지 않는다.
'kotlin' 카테고리의 다른 글
코틀린(Kotlin) : 고차함수 와 람다 (Higher-Order Functions and Lambdas) (0) | 2020.01.02 |
---|---|
코틀린(Kotlin) : 함수(Function) (0) | 2020.01.02 |
코틀린(Kotlin) 클래스(Class) : 객체 표현식 과 선언 (Object Expressions and Declarations) (1) | 2020.01.02 |
코틀린(Kotlin) 클래스(Class) : 제네릭(Generics) (0) | 2020.01.02 |
코틀린(Kotlin) 클래스(Class) : 타입 별명 (Type aliases) (0) | 2019.12.31 |