Implementation by delegation should not delegate methods that are implemented in super-class/super-interface


#1

I have an interface with a number of implementations, some implemented using delegates. These classes have properties and some courtesy functions to set them with simpler syntax, and less code duplication.

data class Position(val x: Double, val y: Double)

class MutableBounds {
  var position: Position

  fun setPosition(x: Double, y: Double) {
    position = Position(x, y)
  }
}

The problem is when I want to delegate to this from some other class, setPosition is delegated, and I’d like to make modifications to var position in a sub-class and setPosition to not be delegated, but let only methods that would be undefined be delegated.

class SideEffectBounds(val bounds): MutableBounds by bounds {
  var position: Position
    get() = bounds.position
    set(value) {
      bounds.position = value
      doSideEffect()
    }

Problem: Given such a class as the above, setPosition() will be delegated to bounds and doSideEffect() will not be called.

Workarounds:

  1. declare setPosition inside this class and set the var manually. This is error-prone, and cumbersome to write and loses the supposed gains of delegates.
  2. Write more interfaces/classes, and only delegate to the exact interface that is required. This is very cumbersome and requires the addition of some abstract class and an interface.
  3. Use extension functions. This may not be suitable in all situations as sometimes polymorphism is required and different classes can optimise differently.
  4. Don’t use delegates. Write out some abstract class DelegateBounds that only delegates the intended functions and calls the rest on itself.

Another solution I tried was separating the concrete implementations into some AbstractMutableBounds

SideEffectBounds(val bounds): AbstractMutableBounds, MutableBounds by bounds

but this still had the same problem, although it did give me a warning. But I still find it to be unexpected that the methods of this class are delegated even though they are defined in this classes super-class. I expect that we would only delegate those methods that are not defined in this class or its super classes. However, the implementation is to delegate everything that can be delegated that is not defined in this class.

Can this behaviour be changed? Can there be a way to achieve this behaviour? If not, how else should I approach this?


#2

I agree with the point. What can be changed is difficult. Ideally the situation is an error (ambiguous implementation - assuming that the parent doesn’t implement the type itself) as using automatically using an implementation in the parent is hidden. In any case I think a warning would be in place for this. Additionally warnings don’t change the language so they can be implemented easier.


#3

It is a warning if the method is defined in a super-class, but if it is a default method in a super-interface there is no warning.

It’d be great to be able to specify either behaviour, eager or “lazy” delegation, the latter is what I’m after here. A kind of delegation that will prioritise its own super-types (classes and interfaces) method defs over delegation.

This would remove a lot of boilerplate and/or potentially confusing extension methods if this was possible to specify this behaviour.


#4

3 or more Members of the closing panel at Kotlin Conf stated delegation was their least favourite feature. Could that because of unexpected behaviour in the example above?