Help us understand the problem. What is going on with this article?

Kotlinのクラス委譲の使い所

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした