アプリ開発備忘録

PlayStationMobile、Android、UWPの開発備忘録

Kotlinで即時実行Delegation

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つのコードは同じ動作となります。
provideDelegateby 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
    }
}

おわりに

これで結構宣言的なコードが書けて便利ではと思っている。