Partial class delegation in Kotlin
Asked Answered
B

2

15

How do I partial delegate methods/fields in Kotlin?

To be specific: here I am trying to inherit class User from interface TraitA and implement field marked: Boolean in the wrapper StateA. That would clean up the User implementation, because marked is just a state field. Note that TraitA can't be a class because I want to use several such interfaces: User() : TraitA by StateA, TraitB by StateB, ..

/* does not compile (Kotlin M12) */
interface TraitA {
    var marked: Boolean

    fun doStaffWithMarked()  // must be overridable
}

class StateA() : TraitA {
    override var marked = false
}

class User() : TraitA by StateA(){
    override fum doStaffWithMarked() {
        //...all fancy logic here...
    }
}

The alternative is to implement all in one place:

class User() : TraitA{
    override var marked = false // ugly code

    override fum doStaffWithMarked() {
        //...
    }
}

Is there a way/pattern that would solve that problem with easy and as little code as possible? Code/bytecode generation is not an option for me.

UPDATE

I was not very clear about that, but please note that doStaffWithMarked() is unique for every User.

So I may suggest a 'half-bad' solution with run-time assertions:

interface TraitA {
    var marked: Boolean

    /* must be overridden */
    fun doStaffWithMarked() = throw UnsupportedOperationException()
}

class StateA() : TraitA {
    override var marked = false
}

class User() : TraitA by StateA() {
    override fum doStaffWithMarked() {
        //...all fancy logic here...
    }
}

The question is still open, since a really good solution would check that doStaffWithMarked() at compilation time.

Brendon answered 17/6, 2015 at 21:8 Comment(3)
isMarked() in TraitA seems redundant. You can just access marked as it is.Whine
isMarked() does a lot of fancy staff and must be overridable for every end userBrendon
I dont think this will be possible anytime soon, because StateA isnt really a full class, you cant instantiate it without providing an implementation for doStaffWithMarked(), so StateA is useless on its own.And having some classes like User which is a complete class and StateA which is an incomplete class is very strange and will be hell to typecheck i guessHawger
W
19

Split up TraitA into two interfaces, then delegate the one and implement the other:

interface TraitA {
    var marked: Boolean
}

interface TraitAPlus : TraitA {
    fun isMarked(): Boolean
}

class StateA() : TraitA {
    override var marked = false
}

class User() : TraitA by StateA(), TraitAPlus {
    override fun isMarked(): Boolean {
        return marked
    }
}
Whine answered 18/6, 2015 at 11:41 Comment(2)
Would be cool to support such a pattern on the language level. Not sure what it would look like, though.Brendon
Kotlin's motto is making things explicit. I guess the compiler could allow you to delegate interfaces to incomplete implementations but this solution makes it obvious what is delegated and what is implemented.Whine
W
1

Here's a version that just inherites from StateA instead of delegating but that's not very nice:

interface TraitA {
    var marked: Boolean

    fun isMarked(): Boolean
}

abstract class StateA() : TraitA {
    override var marked = false
}

class User() : TraitA, StateA() {
    override fun isMarked(): Boolean {
        return marked
    }
}

And here's a somewhat unusal approach where I delegate TraitA to an anonymous instance of StateA

class User() : TraitA by object : StateA() {
    override fun isMarked(): Boolean {
        return marked
    }
} {

}

To be honest though, I'd rather rethink the design of the class hierarchy instead. In particular, you can put method implementations in interfaces (but not property values) so if isMarked() only depends on marked you could just put the implementation for it directly in TraitA. Your code then becomes:

interface TraitA {
    var marked: Boolean

    fun isMarked(): Boolean {
        return marked
    }
}

class StateA() : TraitA {
    override var marked = false
}

class User() : TraitA by StateA() {

}

Edit: Separate answer: https://mcmap.net/q/776491/-partial-class-delegation-in-kotlin

Whine answered 18/6, 2015 at 10:40 Comment(4)
Thanks for the answer. But as you said, those approaches are not very nice. The last one is not what I need since isMarked() should be overridable (see update). The first one with classes kills the whole idea of multi-inheritance. The second one with anonymous objects works, but it is supper ugly.Brendon
I agree. Could you split up TraitA into two interfaces, so that you can delegate one of them and implement the other? See my edit.Whine
Bingo! It works, and I am able to scale it: try.kotlinlang.org/#/UserProjects/oofhklfiv3hi54r971le3l1tth/…Brendon
Could you please extract the last solution into a separate answer, so I would mark it as correct? Thank you.Brendon

© 2022 - 2024 — McMap. All rights reserved.