🦉

Kotlinのクラスの委譲で検討したいこと

2023/11/11に公開

はじめに

Kotlinの中で好きな機能はクラスの委譲(Delegation)です。

ただたまにハマることがあるので、最近気をつけているポイントを書いてみます。

解説

次のようなインタフェースとクラスがあります。

// インタフェース
interface Animal {
    val name: String
    val age: Int

    // デフォルトメソッド
    fun print() {
        println("name=$name, age=$age")
    }
}

// クラス
data class Dog(
    override val name: String,
    override val age: Int
) : Animal

// 委譲を使っているクラス
data class RenamedAnimal(
    val animal: Animal, 
    override val name: String
): Animal by animal

次のコードを動かすと何が出力されるでしょうか?

fun main() {
    val dog = Dog("taro", 10)
    // 出力①
    dog.print()

    val renamed = RenamedAnimal( dog, "jiro")
    // 出力②
    renamed.print()
}

2回のprint関数の呼び出しでは同じ結果が出力されます。

name=taro, age=10
name=taro, age=10

想像通りでしたでしょうか?

これ、仕様通りの当たり前の挙動ではあるんですが、個人的には次のように2回目の出力が変わることを期待してしまうことがよくあるんですよね。

name=taro, age=10
name=jiro, age=10

こんなとき、簡単に解決する方法があります。
それは、インタフェースに定義したデフォルトメソッドを拡張関数に変更することです。

interface Animal {
    val name: String
    val age: Int
}

// 拡張関数
fun Animal.print() {
    println("name=$name, age=$age")
}

この修正をおこなった後で上述のmain関数を動かすと2回目のprintではname=jiro, age=10と出力されます。

まとめ

インタフェースにデフォルトの振る舞いがあるとき、メソッドとしてインタフェース内に置くのか、拡張関数としてインタフェースの外に置くのかは一考の価値があるかもしれません。

個人的にはデータと振る舞いを分けるのが好みなので最近はもっぱら拡張関数を使うようにしています。

Discussion