Kotlinの拡張関数のガイドライン
はじめに
Kotlinいいですよね。
少し前の- 第6回Kotlin勉強会 @ Sansan - connpass で拡張関数のガイドラインの発表がありました。とても良い資料だと思います。最近だんだんKotlin事例が多くなってきたなので、再掲してもっと広まってほしいです。
ちょっとわかりにくい所を少し補足的に付け足したいと思います。
interfaceに拡張関数に書く
スライドの「interface内で拡張関数定義」の部分の補足を足したい思います。
逆にわかりにくくなっているかも?
interfaceを実装しクラス内で使えるようにする
拡張関数は好きなところにかけます。静的に解決されるので注意が必要です。 オレオレ拡張関数の無法地帯だと、思わぬ罠にはまります。 それは、スライドで言われている通りです。
JavaでUtilクラスレベルのものは、グローバルなところに書いてOKだと思いますが、コンテキストが必要な場合は、他の人が使用すとき混乱してしまうかもしれません。
たとえば、Intに時間に関する拡張関数を書いたとします。
val Int.seconds get() = this * 1000 val Int.minutes get() = this.seconds * 60 1.minutes // => 60000
一見良さそうに見えますが、暗黙の前提としてms(ミリ秒)である必要があります。
要件によって、人によっては、s(秒)が基準で6.minutesの期待値が60かもしれません。しかし、すでに存在するのでどちらかを諦めるか、別の名前にするかです。
前提が必要な場合は、interfaceに閉じ込める方法がベターです。 英語力のなさが露呈しますが、以下のようなinterfaceをつくります。
// msであることを前提とする、拡張関数をまとめる interface TimeMsInterface { val Int.seconds get() = this * 1000 val Int.minutes get() = this.seconds * 60 }
使用する場合は、interfaceを実装する。そのclass内で使えるようになります。
class Hoge : TimeMsInterface { val foo = 1.minutes fun hiyo() = 1.seconds }
また、拡張関数をつかえるスコープを限定するとIDEにムダに保管されることもありません。
一時的に拡張関数をつかえるようにする
クラスで実装するまででなく、一時的に使い場合があります。
スライドで乗っている方法だと
interface TimeMsInterface { val Int.seconds get() = this * 1000 val Int.minutes get() = this.seconds * 60 } fun calcTime(f: TimeMsInterface.() -> Int) = object : TimeMsInterface {}.f() calcTime { 1.minutes } // => 60000
???っとなると思いますので、補足をします。
//object式で無名クラスのオブジェクトを作成をします val timeMsInterface = object : TimeMsInterface {} //runをつかってレシーバー付ラムダなのでthisがTimeMsInterfaceになることで、TimeMsInterfaceの内部を使用することが出来る timeMsInterface.run { 1.minutes } // => 60000
このまま関数化する
fun calcTime(f: TimeMsInterface.() -> Int): Int { val timeMsInterface = object : TimeMsInterface {} return timeMsInterface.run(f) }
f: TimeMsInterface.() -> Int は、レシーバーがTimeMsInterfaceになるということだけど、TimeMsInterfaceに一時的な拡張関数f()を作ると考えても良いので。
fun calcTime(f: TimeMsInterface.() -> Int): Int { val timeMsInterface = object : TimeMsInterface {} return timeMsInterface.f() //TimeMsInterfaceに一時的なf()メソッドを使用 }
わざわざ2行にする必要もないので
fun calcTime(f: TimeMsInterface.() -> Int) = object : TimeMsInterface {}.f()
返り値がInt限定にしてるので、Intを返す拡張関数しか使えません。少し発展させてどの拡張関数でも問題ないようにします。
fun<T> calcTime(f: TimeMsInterface.() -> T) = object : TimeMsInterface {}.f()
まとめ
拡張関数はとっても便利だけど、オレオレ度控えめで便利に使っていきましょう。