24
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

KotlinAdvent Calendar 2019

Day 11

Kotlinのクラス委譲の使い所

Last updated at Posted at 2019-12-03

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インスタンスに委譲しているわけですね。

参考

Effective Kotlin Item 36

24
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?