This in kotlin interface delegation

Viewed 528

Is there any way to pass this when using interface delegation? This would enable nice composability - but I found no way to do this. Means something like:

interface Foo {
}

class FooImpl(bar: Bar) : Foo {

}

class Bar: Foo by FooImpl(this) {
}

as long as FooImpl doesnt need a parameter like this it works - but it would be great to access the other class there - perhaps someone knows a way. Otherwise I would also be interested if this is worth a KEEP if not - or if it will be impossible for some reason.

2 Answers

Delegation doesn't support this. The delegate has to be instantiated before the class that is delegating to it, so the delegate cannot rely on it for construction. Another gotcha is that although you can override functions of the delegate, if the delegate internally calls those functions, it calls the original version, not the override. The delegate really lives in its own world.

But you could set it up for the host to pass itself to the delegate in its initialization block:

interface Foo<T> {
    var host: T
    fun doSomething()
}

class FooImpl : Foo<Bar> {
    override lateinit var host: Bar

    override fun doSomething() {
        println(host.name)
    }
}

class Bar(val name: String): Foo<Bar> by FooImpl() {
    init {
        host = this
    }
}

fun main() {
    val bar = Bar("Hello world")
    bar.doSomething()
}

This would unfortunately expose the host to the possibility of getting disconnected from its own delegate by outside classes, though. Maybe you could make the property throw an exception if assigned more than once.

Here's a property delegate that could do that:

private class SingleAssignmentVar<T>: ReadWriteProperty<Any, T> {
    private var value: T? = null
    private var assigned: Boolean = false

    @Suppress("UNCHECKED_CAST")
    override fun getValue(thisRef: Any, property: KProperty<*>): T {
        if (!assigned)
            error("Property has not yet been set.")
        return value as T
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
        if (assigned)
            error("Property may only be set once.")
        assigned = true
        this.value = value
    }
}

fun <T> Delegates.singleAssignment(): ReadWriteProperty<Any, T> = SingleAssignmentVar()

You may split your Bar class in two parts, say backend and frontend.

Frontend will be responsible for declaring interface with delegates, backend will host delegates and act as composition target.

For example:

interface Foo {
    fun sayHello(): String
}

class FooImpl(val bar: BarBackend) : Foo {
    override fun sayHello() = "Hello from ${bar.compositionTarget()}!"
}

class BarBackend() {
    val fooImpl: FooImpl = FooImpl(this)

    fun compositionTarget() = "backend"
}

class BarFrontend(backend: BarBackend) : Foo by backend.fooImpl

fun main() {
    val bar = BarFrontend(BarBackend())
    println(bar.sayHello())
}
Related