Kotlin 委譲(Delegation)

委譲による実装(Implementation by Delegation)

Kotlinは、継承の代替となるDelegation patternを、boilerplate codeなしでサポートしている。

次の例では、Derivedクラスが自身のすべてのpublicメンバーを特定のオブジェクト(BaseImpl)に委譲することで、Baseインターフェースを実装している。

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() {
        print(x)
    }
}

class Derived(b: Base) : Base by b

fun main() {
    val b = BaseImpl(10)
    Derived(b).print()
}

Output:

10

Derivedクラスの上位型リストに書かれたbyは、bDerivedのオブジェクト内部に保存され、コンパイラがbへ転送するBaseのすべてのメソッドを生成することを表す。この例から分かるように、byキーワードは委譲機能を提供する。

もう1つ例を見てみよう。次の例では、Baseインターフェースを実装するBaseImplクラスがあるとする。BaseImplクラスの機能が便利で、BaseImplの一部を一時的に拡張して利用したい場合、Baseインターフェースが要求するプロパティと関数をすべて再定義する必要はない。一部の機能だけを拡張したBaseImplExクラスを作成して、一時的に使用できる。

interface Base {
    fun funcA()
    fun funcB()
}

class BaseImpl : Base {
    override fun funcA() {
        println("AAA")
    }

    override fun funcB() {
        println("BBB")
    }
}

class BaseImplEx(b: Base) : Base by b {
    override fun funcA() {
        println("AAA!!!")
    }
}

fun main() {
    println("== BaseImpl ====")
    val b = BaseImpl()
    b.funcA()
    b.funcB()

    println("== BaseImplEx ====")
    val bx = BaseImplEx(b)
    bx.funcA()
    bx.funcB()
}

Output:

== BaseImpl ====
AAA
BBB
== BaseImplEx ====
AAA!!!
BBB

委譲で実装されたインターフェースメンバーの再定義(Overriding a member of an interface implemented by delegation)

再定義は期待どおりに動作する。コンパイラは、委譲オブジェクトの再定義された実装ではなく、委譲するクラスで再定義された実装を使用する。つまり、Derivedに再定義関数fun printMessage() { print("abc") }を追加した場合、printMessageが呼び出されるとプログラムは10ではなくabcを出力する。

interface Base {
    fun printMessage()
    fun printMessageLine()
}

class BaseImpl(val x: Int) : Base {
    override fun printMessage() { print(x) }
    override fun printMessageLine() { println(x) }
}

class Derived(b: Base) : Base by b {
    override fun printMessage() { print("abc") }
}

fun main() {
    val b = BaseImpl(10)
    Derived(b).printMessage()
    Derived(b).printMessageLine()
}

Output:

abc10

しかし、この方法で再定義されたメンバーは、インターフェースメンバー自身の実装にだけアクセスできる委譲オブジェクトのメンバーからは呼び出されない。

つまり、委譲オブジェクトのメンバーは、インターフェースメンバーの実装だけにアクセスできる。

interface Base {
    val message: String
    fun print()
}

class BaseImpl(x: Int) : Base {
    override val message = "BaseImpl: x = $x"
    override fun print() { println(message) }
}

class Derived(b: Base) : Base by b {
    // このプロパティはbの'print()'実装からアクセスできない。
    override val message = "Message of Derived"
}

fun main() {
    val b = BaseImpl(10)
    val derived = Derived(b)
    derived.print()
    println(derived.message)
}

Output:

BaseImpl: x = 10
Message of Derived

委譲プロパティ(Delegated Properties)

上の例はClass Delegationであり、Property Delegationではプロパティへのアクセスを別のクラスに委譲することもできる。

import kotlin.reflect.KProperty

class Example {
    var p: String by Delegate()
    override fun toString() = "Example"
}

class Delegate() {
    operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
        return "-- Get ${prop.name} from $thisRef"
    }
    operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
        println("-- Set \"$value\" to ${prop.name} in $thisRef")
    }
}

fun main() {
    val e = Example()
    e.p = "NEW STRING"    // -- Set "NEW STRING" to p in Example
    println(e.p)          // -- Get p from Example
}

参考