Takuji->find;

株式会社はてなでアプリケーションエンジニアやってます、技術的な記事を書いているつもり

みんな大好きKotlinのDelegationについて #ktac2015

こんにちは、関西Kotlin勉強会定期開催を目論んでいるtakuji31です。

これはKotlin Advent Calendar 2015、第2日目の記事です。

www.adventar.org

今日はKotlinのDelegationについて紹介します。

Delegationについて

委譲 (delegation) とはオブジェクト指向プログラミングにおいて、あるオブジェクトの操作を一部他のオブジェクトに代替させる手法のこと。

委譲 - Wikipedia

例えばクラスAのオブジェクトある処理を、クラスBに任せるといった仕組みですね。

  • 継承をせずに一部の処理を拡張したい
  • 同じ処理を別クラスに共通して実装したい

といった時に使うことが多いようです。

KotlinのDelegation

KotlinのDelegationは以下の2種類があります。

  • Class Delegation
  • Property Delegation

Class Delegation

Kotlinにおいて、オブジェクトの処理を別クラスのオブジェクトに委譲する仕組みです。

Class Delegation構文を使うと、あるクラスに別クラスの全てのメソッドを呼び出すためのメソッドを自動生成します。

interface A {
    fun hoge()
    val prop : String
}
class AImpl() : A {
    override val prop : String
        get() = "Delegation is pretty!"
    override fun hoge() {
        println("Hello delegation!")
    }
}

class B(a : A) : A by a
class C(val a : A) : A by a {
    override fun hoge() {
        a.hoge()
        println("Override method!")
    }
}

fun main(args : Array<String>) {
    val a = AImpl()
    val b = B(a)
    
    b.hoge() // Hello delegation!
    println(b.prop) // Delegation is pretty!

    val c = C(a)
    c.hoge() // Hello delegation!(line break)Override method!
}

A by aと表記してやることで、インターフェースAで定義されているメソッドやプロパティーの処理を全てaオブジェクトに委譲します。

もちろんoverrideしてやることで、処理を拡張することもできます。

Property Delegation

Class Delegationがクラス全体の処理を委譲する仕組みに対して、Property Delegationはオブジェクトのプロパティー処理のみを委譲する仕組みです。

class DelegatedClass() {
    var nonNullString : String by Delegates.notNull()
}

こちらもbyキーワードで特定のオブジェクト(上記のサンプルコードの場合はDelegates.notNull()で生成されたもの)に処理を移譲します。

こうしてやることで、nonNullStringは初期化不要ですが、getする前に必ず値をsetしてやらないといけないプロパティーになります。

ちなみにDelegatesはデフォルトでProperty Delegationを生成するメソッドがいくつか用意されているobjectです。

Property Delegationを実装する

もちろん自分でオブジェクトを定義することもできます。

ReadWriteProperty、もしくはReadOnlyPropertyを実装したクラスを用意しましょう。

今回はMapからStringをset/getするMapVarPropertyを作ってみます。

class MapVarProperty(val map : MutableMap<String, String>, val key : String? = null) : ReadWriteProperty<Any, String> {
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        val k = key ?: property.name
        return map[k]!!
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
        val k = key ?: property.name
        map[k] = value
    }
}
class MapModel(val map : MutableMap<String, String>) {
    var hoge : String by MapVarProperty(map)
}

fun main(args: Array<String>) {
    val mapModel = MapModel(hashMapOf())

    mapModel.hoge = "This map key is not null!!!!"
    println(mapModel.hoge) // This map key is not null!!!!
}

ReadOnlyPropertyの場合はsetValueの実装が不要で、read onlyなproperty(valで定義されるもの)にしか使えません。

作っておいてなんですが、Mapの値をset/getする機能はKotlin本体に既に組み込まれているので、この場合 by mapとしてやるだけでほぼ同じことができます。

記事内のサンプルコードについて

Githubにアップしました。

github.com

Delegationについてもっと知りたい

もっと知りたい方は、公式ドキュメントをどうぞ。

https://kotlinlang.org/docs/reference/delegation.html https://kotlinlang.org/docs/reference/delegated-properties.html

実際にどう使うかは拙作のKoreferenceが参考になると思います。

github.com

KoreferenceはAndroidのSharedPreferencesをClass DelegationやDelegated Propertyでよしなに扱うライブラリーです。

皆さんもKotlinのDelegationで楽しい委譲ライフをお過ごしください。

明日の担当は@com4dcさんです。楽しみですね!

おまけ

初めてKotlinに言及したtweet

思ったより最近だった。