Kotlinのクラス委譲(class delegation)機能について、今まで文法的には理解していつつも使い所がイマイチわかっていなかったんですが、今回一つの使い所がわかったので紹介します。
Kotlinドキュメント(本家)
Kotlinドキュメント(日本語版)
※ 余談ですがクラス委譲という呼び名の他に、インターフェース委譲という呼び名もあるようです。意味的にはそちらのほうがしっくり来ますが、よく聞くクラス委譲という呼び名をここでは採用します。
クラス委譲の文法
詳細は前述の本家ドキュメントに書かれているので、ここでは超簡単に紹介します。
以下のようなやつです。
interface UserRepository {
fun list()
}
class UserRepositoryImpl(val companyId: Long) : UserRepository {
override fun list() { TODO() }
}
class UserService(repo: UserRepository) : UserRepository by repo
fun main() {
val repo = UserRepositoryImpl(10)
//UserServiceクラスで定義していないlistというメソッドが使える!!(このメソッドはUserRepositoryImplのものが呼び出される)
UserService(repo).list()
}
※ 注:頭に入ってきやすくするためRepository
,Service
等の命名をしていますが、本来的にはこういう単にコード行数を短くするためだけにクラス委譲を使うのは実装として微妙だと思います。
使い所
結論から言うと、ライブラリにあるインターフェースを継承し、何かしらの機能拡張したクラスを作るケースが一つの使い所になると思います。
例えば標準ライブラリにあるListインターフェースを継承したIdListクラスというものを独自で作って、ただのList<Int>
とは違った挙動にしたいと考えたとします(Idならではの制約を入れたり、新しいメソッドを生やしたり…etc)。
まずは以下のように書くと当然コンパイルエラーになります。
//ERROR:コンパイルエラー!!
class IdList : List<Int>
Listインターフェースに定義されている必要なメソッドがないからですね。
実装してみましょう。
class IdList : List<Int> {
//LinkedListを使っているのに深い意味はない。emptyListOfだとList<Int>型になり、後の説明でややこしくなるため。
private val idList = LinkedList<Int>()
override val size: Int
get() = idList.size
override fun contains(element: Int): Boolean {
return idList.contains(element)
}
override fun containsAll(elements: Collection<Int>): Boolean {
return idList.containsAll(elements)
}
override fun get(index: Int): Int {
return idList.get(index)
}
override fun indexOf(element: Int): Int {
return idList.indexOf(element)
}
override fun isEmpty(): Boolean {
return idList.isEmpty()
}
override fun iterator(): Iterator<Int> {
return idList.iterator()
}
override fun lastIndexOf(element: Int): Int {
return idList.lastIndexOf(element)
}
override fun listIterator(): ListIterator<Int> {
return idList.listIterator()
}
override fun listIterator(index: Int): ListIterator<Int> {
return idList.listIterator(index)
}
override fun subList(fromIndex: Int, toIndex: Int): List<Int> {
return idList.subList(fromIndex, toIndex)
}
}
・・・思いの外長くなってしまいました。
(正直筆者も夜中にこれを書いていて、何をやっているんだろうという気になりました)
この中のどれが独自メソッドで、どれがIdListならではの特殊な処理を行っているメソッドなのか、パッとはわからないですね。
ときめかないので、こんまりしましょう。
クラス委譲を使います。
class IdList(private val idList: LinkedList<Int>) : List<Int> by idList
見事キレイに掃除が完了しました。
考察
このクラス委譲という機能は自身が拡張機能として書きたいロジックに集中するための機能だという理解をしています。
上の例だとListインターフェースが持つ振る舞いは基本的にidList: LinkedList<Int>
のものをそのまま使い、自身で定義したいロジックだけを追加で定義することができます。
クラス委譲
の名前のとおり、基本的な振る舞いはidList
インスタンスに委譲しているわけですね。