6

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.

Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161

6 Answers6

6

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()
        // ...
    }
}
Egor Neliuba
  • 14,784
  • 7
  • 59
  • 77
  • I did consider that, but unfortunately that is not an option in my more specific use case. I should likely update the question. In my concrete use case, I did not use `Base by b`, but `Base by BaseImpl()` (I do not want to use dependency injection). Therefore, I can not reference `b` in the overridden function. – Steven Jeuris Dec 14 '17 at 10:31
  • 1
    @StevenJeuris you can do `class Derived(val b: Base = BaseImpl()) : Base by b` as a workaround. – Egor Neliuba Dec 14 '17 at 10:39
  • In that case the caller can pass and hold on to `BaseImpl` which is exactly what I want to avoid. Some more context (but likely irrelevant for this question): I am creating an aggregate root (DDD) which simply reuses an existing partial implementation. – Steven Jeuris Dec 14 '17 at 10:44
  • Your answer did provide inspiration on how to address this more concrete problem, [which I posted as a separate answer](https://stackoverflow.com/a/47811932/590790). – Steven Jeuris Dec 14 '17 at 11:13
6

@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.

Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161
  • 1
    This works only if you make your class `final` / non-`open` or share `b` with children classes. See [my answer](https://stackoverflow.com/a/47842308/3755692) for a solution where you can make `b` private – msrd0 Dec 16 '17 at 03:41
  • Thank you @msrd0! In the end I went with this answer, but using [a secondary constructor](https://stackoverflow.com/a/47842308/590790) as suggested in your answer could definitely be useful in case not having a base class for composition (`HiddenBaseImpl`) is preferred. – Steven Jeuris Dec 19 '17 at 08:41
3

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
}
msrd0
  • 7,816
  • 9
  • 47
  • 82
  • 'val' on secondary constructors are not allowed, so this way I can not introduce readonly variables in the constructor. – Steven Jeuris Dec 19 '17 at 08:25
  • @StevenJeuris You'll have to add additional parameters on both constructors and pass them from the secondary to the primary ctor where you can make them an instance variable – msrd0 Dec 19 '17 at 08:27
  • Thank you for the tip! I understand now. However, this has just become one messy constructor(s). :/ I think I prefer [my solution](https://stackoverflow.com/a/47811932/590790) in the end for my specific use case, where `Derived` is supposed to be final. The `HiddenBaseImpl` becomes something like a 'composition root', which delegates the implementation of components which need to be implemented to a base class. – Steven Jeuris Dec 19 '17 at 08:37
  • My main need was that `Derived` could not be modified, but could easily delegate parts of its implementation to contained components. Interesting how we have three answers for slightly different use cases now. :) Don't really know which one to accept. – Steven Jeuris Dec 19 '17 at 08:38
0

I think I have a better solution for this. It is more elegant in sense of that there is no extra class needed and it does exactly the same as the approved one.

interface Base {
    fun print()
}

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

    override fun print{
      delegate.print()
    }
}
Sergey Dryganets
  • 899
  • 8
  • 18
0

I just want to add on to the solution for this case when using Dependency Injection.

You may use a secondary constructor to receive your injections, then use the primary constructor to construct your delegated object.

class BaseImpl(injectedObject: Object)

class Derived(
  private val baseImpl: BaseImpl,
): Base by baseImpl {
  
  // Used to construct your object by Dagger or any other DI Framework
  @Inject constructor(injectedObject: Object): this(BaseImpl(injectedObject))

  override fun print() {
    baseImpl.print()
    // Do your own thing
  } 
}
PenguinDan
  • 904
  • 7
  • 15
-1

You could call directly function print() from property b, but for this purpose you need to change from simple constructor parameter b: Base to saving parameter as property private val b: Base

Hope this will help you.

kurt
  • 1,510
  • 2
  • 13
  • 23
  • When defined as a property, the delegation definition (`Base by b`) does not work. – Steven Jeuris Dec 14 '17 at 10:39
  • As property in constructor. Why delegation will not work for this? interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } class Derived(private val b: Base) : Base by b { } fun main(args: Array) { val b = BaseImpl(10) Derived(b).print() // prints 10 } – kurt Dec 14 '17 at 11:01
  • Thank you for clarifying, I did not understand you intended a property in the constructor. Perhaps clarify by adding a fuller code sample (so I can also remove the down vote). – Steven Jeuris Dec 14 '17 at 11:02
  • When you include your comment in your answer (you can edit and are always encourage to do so) I can remove the down vote. As it stands now, the down vote is 'locked in' by stackoverflow since it did not change since I added the down vote. – Steven Jeuris Dec 14 '17 at 11:15