Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

AndroidではContextを第1引数によく取る class MyView: FrameLayout { // 各コンストラクタは省略 init { // LayoutInflaterオブジェクトが欲しい LayoutInflater.from(context) .inflate(R.layout.my_view, this) } }

Slide 7

Slide 7 text

class MyView: FrameLayout { // 各コンストラクタは省略 init { // LayoutInflaterオブジェクトが欲しい LayoutInflater.from(context) .inflate(R.layout.my_view, this) } } AndroidではContextを第1引数によく取る getContext()でも同じ

Slide 8

Slide 8 text

そんなときはエクステンション class MyView: FrameLayout { init { // LayoutInflaterオブジェクトが欲しい context.layoutInflater() .inflate(R.layout.my_view, this) } } fun Context.layoutInflater(): LayoutInflater = LayoutInflater.from(this)

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

似ているエクステンションを量産? class MyAdapter: ArrayAdapter { override fun getView(...) { // LayoutInflaterオブジェクトが欲しい layoutInflater() .inflate(R.layout.item_view, null) } } fun ArrayAdapter<*>.layoutInflater(): LayoutInflater = LayoutInflater.from(context)

Slide 12

Slide 12 text

インタフェースで共通部分を抽出 interface ContextHolder { fun getContext(): Context } fun ContextHolder.layoutInflater(): LayoutInflater = LayoutInflater.from(getContext())

Slide 13

Slide 13 text

ContextHolderと拡張関数1個で賄える class MyAdapter: ArrayAdapter, ContextHolder { override fun getView(...) { layoutInflater().inflate(...) } } class MyView: FrameLayout, ContextHolder { init { layoutInflater().inflate(...) }

Slide 14

Slide 14 text

2. partial class的なやつ

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

大きくなりがちな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なヘルパーメソッド

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

インタフェース+エクステンションで分割 class MainActivity: AppCompatActivity(), MainActivityHelper { override fun onCreate(bundle: Bundle?) { super.onCreate(bundle) prepare() setupViews() } interface MainActivityHelper { fun MainActivity.prepare() {...} fun MainActivity.setupViews() {...} } ヘルパーメソッドを 拡張関数として定義

Slide 19

Slide 19 text

公開されているレシーバのAPIが使える interface MainActivityHelper { fun MainAcitivty.prepare() { ... } fun MainActivity.setupViews() { setContentView(R.layout.activity_main) textView = findViewById(R.id.text_view) } }

Slide 20

Slide 20 text

3. Double Context Extension

Slide 21

Slide 21 text

こんな関数呼び出しをしたい! class MyActivity: AppCompatActivity() { ... fun showMessage() { // myDialog.show(supportFragmentManager, "tag") myDialog.show("tag") } }

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

class MyActivity: AppCompatActivity() { ... fun showMessage() { // myDialog.show(supportFragmentManager, "tag") myDialog.show("tag") } } こんな関数呼び出しをしたい! DialogFragmentに拡張関数showを生やせば? いや、プロパティsupportFragmentManagerが必要だ...  →FragmentManagerに依存

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

方法1: エクステンション in インタフェース interface DialogFeature { fun getSupportFragmentManager(): FragmentManager fun DialogFragment.show(tag: String) { show(getSupportFragmentManager(), tag) } } class MyActivity: AppComatActivity(), DialogFeature { ... fun showMessage() { myDialog.show("tag) } インタフェースを 実装したくない!

Slide 26

Slide 26 text

方法2: Double Context Extension ● 2つのコンテキストを持った拡張関数 ● 命名 by 私 ● 見た目: 型Aの定義中で、型Aに依存しながらも型Bの拡張関 数を生やすことができる class MyActivity: AppCompatActivity() { ... fun showMessage() { myDialog.show("tag") } } この関数は、引数の他に DialogFragmentとFragmentActivity の2つに依存する

Slide 27

Slide 27 text

タネ明かし ● 実際のコード: 「B型の拡張関数」を返す「A型の拡張プロパ ティ」を定義する // 別の適当なファイル val FragmentActivity.show: DialogFragment.(String)->Unit get() = { tag -> show(supportFragmentManager, tag) }

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

どう解釈されるのか 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")

Slide 36

Slide 36 text

すごく ない?

Slide 37

Slide 37 text

いや、 わけわか らん

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

まとめ ● 拡張関数って便利だよね ● でも限界がある ● インタフェースと組み合わせる ● Double Context Extensionという提案 ● 変なことしないで素直なコードを心がけましょう

Slide 40

Slide 40 text

エムスリーで 僕とKotlin