Delegated Properties で遊ぼう(スライド版)

  • 11
    Like
  • 0
    Comment
More than 1 year has passed since last update.

そのうちちゃんと記事書きます。

だれ

  • icon_128.png @kikuchy
  • 株式会社ミクシィ -> 株式会社Diverse
  • Android書いてるおじさん

前回、KotlinでDSL作るはなしをした者です。

:point_right: 3分で作る Kotlin Friendly な API


Delegated Propertyとは?

  • クラスのプロパティ(メンバ)のsetter/getter処理を別のオブジェクトに移譲する機能
  • 特定のメソッドさえ実装していれば、移譲先オブジェクトになれる

何がうれしいの?


遅延処理ができる!!!!!!


// Activityの中

lateinit var hoge:String

...

override fun onCreate(...) {
    // Fragment の arguments についても同様の悩みが
    hoge = intent.getStringExtra("hoge") ?: ""
}

コンストラクタで初期化できないばっかりに、
val の恩恵を受けられない! :anger: :anger: :anger:

lateinitvar のみ使用可能)


ボイラープレートを大幅に減らすことができる!!!


// Fragmentの中

var listener: HogeFragmentActionListener? = null

override onAttach(...) {
    if (activity is HogeFragmentActionListener) {
        listener = activity
    } else {
        throw Exception("""
  ActivityHogeFragmentActionListenerを実装してないとだめ
        """)
    }
}

これFragmentにいちいち書かなきゃダメ????


Delegated Propertiesで解決 :muscle:


使い方 (1)

  • 必要なメソッドを実装
    • Extension で実装させてもOK
// 移譲元クラスが R、プロパティの型が T の場合

operator fun getValue(
        thisRef: R, property: KProperty<*>): T

// var 宣言したプロパティに使う場合は setValue も必要
operator fun setValue(
        thisRef: R, property: KProperty<*>, value: T)

使い方 (2)

  • by キーワードで移譲するインスタンスを設定
class R {
    // HogeDelegate が getValueを実装しているとして
    val hoge: T by HogeDelegate()

    // FugaDelegate が getValue/setValueを実装しているとして
    var fuga: T by FugaDelegate()

...

✧\\ ٩( 'ω' )و //✧


今すぐ使える例


標準の lazy

プロパティが初めて呼ばれた時にブロック内が実行される。
以降の呼び出しでは、ブロックで返した値が使われる。

val layoutManager: LinearLayoutManager by lazy {
    LinearLayoutManager(this)
}

標準の Map<K, V>

Map<K, V> には getValue のExtensionがあるので、そのまま移譲先になれる。

class Hoge(val map: Map<String, String?>) {

    // map["fuga"] の値が返る
    val fuga: String? by map

...

MutableMap<K, V> には setValue もあるので、 var の移譲先になれる。


Kotter Knife

ButterKnifeをKotlinらしく実現した、Jake神謹製ライブラリ

val userName: EditText by bindView(R.id.user_name)

ktファイル1つというシンプルさなので、Delegated Propertyの勉強にも最適。


応用


Intent, Bundleからの取得

class IntentStringDelegate {
   operator fun getValue(
     thisRef: Activity, property: KProperty<*>): String?
       = thisRef.intent.getStringExtra(property.name)
}

...

val hoge: String? by IntentStringDelegate()

...

// textView.text = intent.getString("hoge") 
textView.text = hoge

Fragmentのリスナー取得 (1)

inline fun <reified T> listenerOfFragment(): ReadOnlyProperty<Fragment, T>
        = object : ReadOnlyProperty<Fragment, T> {
    private var value: T? = null

    override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
        if (value == null)
            value = if (thisRef.parentFragment != null && thisRef.parentFragment is T) {
                thisRef.parentFragment as T
            } else {
                try {
                    thisRef.context as T
                } catch (e: ClassCastException) {
                    throw ClassCastException("${thisRef.context.toString()} or parent Fragment must implement ${T::class.simpleName}")
                }
            }
        @Suppress("UNCHECKED_CAST")
        return value as T
    }
}

Fragmentのリスナー取得 (2)

private val loginListener: LoginListener
        by listenerOfFragment()

...

loginListener.login(id, password)

遊んでみる


おみくじ

class OmikujiProperty{
    private val random = Random()

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return when(random.nextInt(4)) {
            0 -> "大吉"
            1 -> "中吉"
            2 -> "小吉"
            else -> "凶"
        }
    }
}

class Application {
    val omikuji: String by OmikujiProperty()

}
fun main(args: Array<String>) {
    val app = Application()
    print(app.omikuji)
}

:point_down:

kikuchy omikuji_kotlin $ ./gradlew :run
(略)
大吉
BUILD SUCCESSFUL

Total time: 6.161 secs

kikuchy omikuji_kotlin $ ./gradlew :run
(略)
凶
BUILD SUCCESSFUL

Total time: 6.1 secs

kikuchy omikuji_kotlin $ ./gradlew :run
(略)
凶
BUILD SUCCESSFUL

Total time: 5.993 secs

kikuchy omikuji_kotlin $ ./gradlew :run
(略)
大吉
BUILD SUCCESSFUL

Total time: 5.988 secs

kikuchy omikuji_kotlin $ ./gradlew :run
(略)
小吉
BUILD SUCCESSFUL

Total time: 5.955 secs

Tips

  • 以前は特定インターフェースを継承したオブジェクトが移譲先になれた
    • ReadOnlyProperty
    • ReadWriteProperty
  • getValue / setValue の第一引数 thisRef の型を指定すると、指定したクラスのプロパティにしか使えない。
  • 逆に Any? にすると、どのクラスのプロパティでも使用可能。

質問?


icon_128.png @kikuchy