Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

by kikuchy
1 / 25

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

だれ

  • 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("""
  ActivityはHogeFragmentActionListenerを実装してないとだめ
        """)
    }
}

これ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

kikuchy
FlutterやiOS、Androidのアプリ作ったりして生きています
http://kikuchy.hatenablog.com
diverse
結婚支援事業を中心に、友達・恋人探しのマッチング事業を展開。深刻化する恋愛離れ、未婚率の上昇を解決すべくWEB・アプリサービスを展開。すべての人へ出会いのプラットフォームを提供しています。
http://diverse-inc.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした