KotlinのDelegation、便利ですよね。プロパティの名前を取得できるという点が便利で、Annotation Processorの代わりに使われたりもします(代替の技術ではないけど)。
紹介する機能
今回はDelegationの Providing a delegate
という機能を知ったため、これを紹介します。Kotlin1.1からあったのに知らなかった。
Delegated Properties - Kotlin Programming Language
Delegation
以下の簡単なDelegationのコードがあります。
この場合、nameが呼ばれるたびにDelegateのgetValueが呼ばれます。
class Hoge() { val name: String by Delegate("") class Delegate<T, R>( private val value: R ) : ReadOnlyProperty<T, R> { override fun getValue(thisRef: T, property: KProperty<*>): R { return value } } }
やりたいこと
例えば上のコードで一回のみ、nameのプロパティ名を使って何かに登録してIdを貰うとします。nameは呼ばれても呼ばれなくてもregisterには登録したいものとします。
class Register { fun register(property: String, value: String): Int { TODO() } }
上記のDelegationではフィールドを呼んだ時に初めてDelegationの中身が呼ばれて、工夫をしないと毎回呼ばれてしまいます。
Providing a delegate
そこで Providing a delegate
を使います。
KotlinのクラスにはprovideDelegateというオペレータがあり、これを実装したクラスを移譲するとprovideDelegateの戻り値が返されます。
operator fun <T> provideDelegate( thisRef: T, prop: KProperty<*> )
試しに以下のクラスを作成しました。
class ProvideDelegateSample { operator fun <T> provideDelegate( thisRef: T, prop: KProperty<*> ) = lazy { "" } }
以下の2つのコードは同じ動作となります。
provideDelegate
は by ProvideDelegateSample()
定義時に即時呼び出されます。
val value by ProvideDelegateSample() val value by lazy { "" }
実装
provideDelegateオペレータを使用してやりたいことを実装します。
class Hoge() { private val register = Register() val name by delegate("") fun delegate(value: String): CreateInstanceWithPropertyName<Int> { return CreateInstanceWithPropertyName { // 移譲元に返す値を作成する register.register(it, value) } } } class CreateInstanceWithPropertyName<R>( private val value: (String) -> R ) { operator fun <T> provideDelegate( thisRef: T, prop: KProperty<*> ): Delegate<T, R> { // フィールド名を渡して移譲元に返す値を作成させる return Delegate(value(prop.name)) } class Delegate<T, R>( private val value: R ) : ReadOnlyProperty<T, R> { // 受け取った値をそのまま返すだけ。 override fun getValue(thisRef: T, property: KProperty<*>) = value } }
おわりに
これで結構宣言的なコードが書けて便利ではと思っている。