Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling (base) delegated function when using class delegation from within override

When overriding an interface method implemented by class delegation, is it possible to call the class which is normally delegated to from within an overriding function? Similar to how you would call super when using inheritance.

From the documentation:

interface Base {
    fun print()
}

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

class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // prints 10
}

Note that overrides work as you might expect: The compiler will use your override implementations instead of those in the delegate object.

override fun print() { ... }

How to call the BaseImpl print() function from within this overridden function?

The use case is I want to add additional logic to this function, while reusing the existing implementation.

like image 670
Steven Jeuris Avatar asked Dec 14 '17 10:12

Steven Jeuris


3 Answers

@Egor's answer works and should be suitable when the class delegated to is passed as a paremeter (dependency injection).

However, in my more specific use case (apologies for not having made this clear in my question) the implementation is directly defined in the delegation definition.

class Derived(b: Base) : Base by BaseImpl()

In this case Egor's answer does not work and a more elaborate approach is needed. The following implementation can be used in case you want to keep the implementation hidden so that callers can not modify it. For example, in my specific use case I am creating an aggregate root and want to protect internal invariants.

open class HiddenBaseImpl internal constructor( protected val _b: Base ) : Base by _b

class Derived() : HiddenBaseImpl( BaseImpl() )
{
    override fun print()
    {
        _b.print()

        ... do something extra
    }
}

Since HiddenBaseImpl's primary constructor can only be used internally, callers of the library are unable to instantiate this class and are thus forced to use Derived. Derived can now call the class delegated to internally and add additional behavior without allowing callers to pass different implementations of Base.

like image 81
Steven Jeuris Avatar answered Nov 09 '22 21:11

Steven Jeuris


Since Base is an interface, you can't really call super on it (similar to Java).

Instead, you need to declare b as a field and use it directly:

class Derived(val b: Base) : Base by b {
    override fun print() {
        b.print()
        // ...
    }
}
like image 38
Egor Neliuba Avatar answered Nov 09 '22 21:11

Egor Neliuba


You can only use super in Kotlin to access the superclass, no interfaces or other strange things (remember - Kotlin is running on the JVM). However, it is absolutely fine if you store the derivate instance in a variable, like Egor suggested in his answer.

To avoid that anybody can set or retrieve the variable, you can use a private (or protected, whatever suits your use case) primary constructor and add a second public constructor:

interface Base {
    fun print()
}

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

class Derived private constructor(private val b : Base) : Base by b {
    constructor() : this(BaseImpl())

    override fun print() = b.print()
}

fun main(args: Array<String>) {
    val d = Derived()
    d.b // does not work, compiler error
    d.print() // prints 10
}
like image 3
msrd0 Avatar answered Nov 09 '22 20:11

msrd0