一歩進んだ拡張関数の活用とその濫用で後悔した話 #m3kt

一歩進んだ拡張関数の活用とその濫用で後悔した話 #m3kt

14c9795d267f5b85abb98ca5e8780646?s=128

Taro Nagasawa

August 25, 2017
Tweet

Transcript

  1. 一歩進んだ拡張関数の活用 とその濫用で後悔した話 どこでもKotlin #1 2017-08-24 長澤太郎 @ngsw_taro

  2. 自己紹介 • 長澤 太郎(たろーって呼んでね) • @ngsw_taro • エムスリー株式会社 • やすべえとディズニーが大好き!

  3. 宣伝 9/29発売! 好評発売中 無料で配布中 goo.gl/5vUT7o 鋭意執筆中! Kotlin in Action

  4. もくじ 1. エクステンションからインタフェースへ 2. partial class的なやつ 3. Double Context Extension

  5. 1. エクステンションから インタフェースへ

  6. AndroidではContextを第1引数によく取る class MyView: FrameLayout { // 各コンストラクタは省略 init { //

    LayoutInflaterオブジェクトが欲しい LayoutInflater.from(context) .inflate(R.layout.my_view, this) } }
  7. class MyView: FrameLayout { // 各コンストラクタは省略 init { // LayoutInflaterオブジェクトが欲しい

    LayoutInflater.from(context) .inflate(R.layout.my_view, this) } } AndroidではContextを第1引数によく取る getContext()でも同じ
  8. そんなときはエクステンション class MyView: FrameLayout { init { // LayoutInflaterオブジェクトが欲しい context.layoutInflater()

    .inflate(R.layout.my_view, this) } } fun Context.layoutInflater(): LayoutInflater = LayoutInflater.from(this)
  9. さらに短く class MyView: FrameLayout { init { // LayoutInflaterオブジェクトが欲しい layoutInflater()

    .inflate(R.layout.my_view, this) } } fun View.layoutInflater(): LayoutInflater = LayoutInflater.from(context)
  10. さらに短く class MyView: FrameLayout { init { // LayoutInflaterオブジェクトが欲しい layoutInflater()

    .inflate(R.layout.my_view, this) } } fun View.layoutInflater(): LayoutInflater = LayoutInflater.from(context) Viewでしか使えない
  11. 似ているエクステンションを量産? class MyAdapter: ArrayAdapter<MyItem> { override fun getView(...) { //

    LayoutInflaterオブジェクトが欲しい layoutInflater() .inflate(R.layout.item_view, null) } } fun ArrayAdapter<*>.layoutInflater(): LayoutInflater = LayoutInflater.from(context)
  12. インタフェースで共通部分を抽出 interface ContextHolder { fun getContext(): Context } fun ContextHolder.layoutInflater():

    LayoutInflater = LayoutInflater.from(getContext())
  13. ContextHolderと拡張関数1個で賄える class MyAdapter: ArrayAdapter<MyItem>, ContextHolder { override fun getView(...) {

    layoutInflater().inflate(...) } } class MyView: FrameLayout, ContextHolder { init { layoutInflater().inflate(...) }
  14. 2. partial class的なやつ

  15. 大きくなりがちなActivity class MainActivity: AppCompatActivity() { var textView: TextView? = null

    override fun onCreate(bundle: Bundle?) { super.onCreate(bundle) prepare() setupViews() } private fun prepare() {...} private fun setupViews() {...} }
  16. 大きくなりがちなActivity class MainActivity: AppCompatActivity() { var textView: TextView? = null

    override fun onCreate(bundle: Bundle?) { super.onCreate(bundle) prepare() setupViews() } private fun prepare() {...} private fun setupViews() {...} } privateなヘルパーメソッド
  17. インタフェース+エクステンションで分割 class MainActivity: AppCompatActivity(), MainActivityHelper { override fun onCreate(bundle: Bundle?)

    { super.onCreate(bundle) prepare() setupViews() } interface MainActivityHelper { fun MainActivity.prepare() {...} fun MainActivity.setupViews() {...} }
  18. インタフェース+エクステンションで分割 class MainActivity: AppCompatActivity(), MainActivityHelper { override fun onCreate(bundle: Bundle?)

    { super.onCreate(bundle) prepare() setupViews() } interface MainActivityHelper { fun MainActivity.prepare() {...} fun MainActivity.setupViews() {...} } ヘルパーメソッドを 拡張関数として定義
  19. 公開されているレシーバのAPIが使える interface MainActivityHelper { fun MainAcitivty.prepare() { ... } fun

    MainActivity.setupViews() { setContentView(R.layout.activity_main) textView = findViewById(R.id.text_view) } }
  20. 3. Double Context Extension

  21. こんな関数呼び出しをしたい! class MyActivity: AppCompatActivity() { ... fun showMessage() { //

    myDialog.show(supportFragmentManager, "tag") myDialog.show("tag") } }
  22. class MyActivity: AppCompatActivity() { ... fun showMessage() { // myDialog.show(supportFragmentManager,

    "tag") myDialog.show("tag") } } こんな関数呼び出しをしたい! これを渡すのはお決まりだから 省略したい
  23. class MyActivity: AppCompatActivity() { ... fun showMessage() { // myDialog.show(supportFragmentManager,

    "tag") myDialog.show("tag") } } こんな関数呼び出しをしたい! DialogFragmentに拡張関数showを生やせば? いや、プロパティsupportFragmentManagerが必要だ...  →FragmentManagerに依存
  24. 方法1: エクステンション in インタフェース interface DialogFeature { fun getSupportFragmentManager(): FragmentManager

    fun DialogFragment.show(tag: String) { show(getSupportFragmentManager(), tag) } } class MyActivity: AppComatActivity(), DialogFeature { ... fun showMessage() { myDialog.show("tag) }
  25. 方法1: エクステンション in インタフェース interface DialogFeature { fun getSupportFragmentManager(): FragmentManager

    fun DialogFragment.show(tag: String) { show(getSupportFragmentManager(), tag) } } class MyActivity: AppComatActivity(), DialogFeature { ... fun showMessage() { myDialog.show("tag) } インタフェースを 実装したくない!
  26. 方法2: Double Context Extension • 2つのコンテキストを持った拡張関数 • 命名 by 私

    • 見た目: 型Aの定義中で、型Aに依存しながらも型Bの拡張関 数を生やすことができる class MyActivity: AppCompatActivity() { ... fun showMessage() { myDialog.show("tag") } } この関数は、引数の他に DialogFragmentとFragmentActivity の2つに依存する
  27. タネ明かし • 実際のコード: 「B型の拡張関数」を返す「A型の拡張プロパ ティ」を定義する // 別の適当なファイル val FragmentActivity.show: DialogFragment.(String)->Unit

    get() = { tag -> show(supportFragmentManager, tag) }
  28. どう解釈されるのか val FragmentActivity.show: DialogFragment.(String)->Unit get() = { tag -> show(supportFragmentManager,

    tag) } class MyActivity: AppCompatActivity() { ... fun showMessage() { myDialog.show("tag") } }
  29. どう解釈されるのか val FragmentActivity.show: DialogFragment.(String)->Unit get() = { tag -> show(supportFragmentManager,

    tag) } class MyActivity: AppCompatActivity() { ... fun showMessage() { myDialog.show("tag") } }
  30. どう解釈されるのか val FragmentActivity.show: DialogFragment.(String)->Unit get() = { tag -> show(supportFragmentManager,

    tag) } class MyActivity: AppCompatActivity() { ... fun showMessage() { myDialog.show("tag") } } myDialog.(this.show)("tag") と書いても同じ
  31. どう解釈されるのか val FragmentActivity.show: DialogFragment.(String)->Unit get() = { tag -> show(supportFragmentManager,

    tag) } class MyActivity: AppCompatActivity() { ... fun showMessage() { myDialog.show("tag") } }
  32. どう解釈されるのか val FragmentActivity.show: DialogFragment.(String)->Unit get() = { tag -> show(supportFragmentManager,

    tag) } class MyActivity: AppCompatActivity() { ... fun showMessage() { myDialog.show("tag") } }
  33. どう解釈されるのか val FragmentActivity.show: DialogFragment.(String)->Unit get() = { tag -> show(supportFragmentManager,

    tag) } class MyActivity: AppCompatActivity() { ... fun showMessage() { myDialog.show("tag") } }
  34. どう解釈されるのか val FragmentActivity.show: DialogFragment.(String)->Unit get() = { tag -> show(supportFragmentManager,

    tag) } class MyActivity: AppCompatActivity() { ... fun showMessage() { myDialog.show("tag") } }
  35. どう解釈されるのか val FragmentActivity.show: DialogFragment.(String)->Unit get() = { tag -> show(supportFragmentManager,

    tag) } class MyActivity: AppCompatActivity() { ... fun showMessage() { myDialog.show("tag") } } A.(B)->Cは(A, B)->Cと 見なせる性質を利用して show(myDialog, "tag") or show.invoke(myDialog, "tag")
  36. すごく ない?

  37. いや、 わけわか らん

  38. まとめ • 拡張関数って便利だよね • でも限界がある • インタフェースと組み合わせる • Double Context

    Extensionという提案
  39. まとめ • 拡張関数って便利だよね • でも限界がある • インタフェースと組み合わせる • Double Context

    Extensionという提案 • 変なことしないで素直なコードを心がけましょう
  40. エムスリーで 僕とKotlin