Using the chain of responsibility pattern I ran into a problem where next chain element was expected to have the same generic type of the first element. I know why this happens: The first handler expects the second handler to use the generic type "Apple". I just don't know exactly how to solve it.
There is an answer on how to handle it in java here, but since java doesn't have reified types and all that the approach should look different in Kotlin, right?
There are different options that come to my mind:
To illustrate the problem I am posting a demo code below.
data class Apple(val name:String, val color:Int)
data class Orange(val circumference:Double)
object Main{
@JvmStatic
fun main(args: Array<String>) {
val first = FirstHandler()
val second = SecondHandler()
first.setNextHandler(second) // !!! wrong type here since <Apple> is expected
first.process()
}
}
abstract class ChainHandler<T>{
protected var nextHandlerInChain:ChainHandler<T>? = null
fun setNextHandler(handler: ChainHandler<T>): ChainHandler<T> {
this.nextHandlerInChain = handler
return handler
}
abstract fun peel(): Collection<T>
abstract fun process():MutableMap<String,Any> }
class FirstHandler : ChainHandler<Apple>() {
override fun peel(): Collection<Apple> {
return Collections.emptyList<Apple>()
}
override fun process(): MutableMap<String, Any> {
val peeledApples = peel()
val map = nextHandlerInChain?.process()
map?.put("apples",peeledApples) ?:kotlin.run {
val map = mutableMapOf<String,Any>()
map.put("apples",peeledApples)
}
return map!!
} }
class SecondHandler : ChainHandler<Orange>() {
override fun peel(): Collection<Orange> {
return Collections.emptyList<Orange>()
}
override fun process(): MutableMap<String, Any> {
val peeledOranges = peel()
val map = nextHandlerInChain?.process()
map?.put("oranges",peeledOranges) ?:kotlin.run {
val map = mutableMapOf<String,Any>()
map.put("oranges",peeledOranges)
}
return map!!
}
}
Kotlin has something called a star projection that might help you here. It basically tells the compiler that you don't really care what type of ChainHandler
you get. You can use it to make your setNextHandler
compile, like this:
abstract class ChainHandler<T>{
// Note the star projection here
protected var nextHandlerInChain: ChainHandler<*>? = null
// Defining a type parameter S, so that the return type is equal to the input type.
fun <S> setNextHandler(handler: ChainHandler<S>): ChainHandler<S> {
this.nextHandlerInChain = handler
return handler
}
...
}
You can read more about star projections here: https://kotlinlang.org/docs/reference/generics.html#star-projections
As for reifying the type parameter: Reified type parameters work only for inline functions. Not for type parameters on a class.
This very much depends on specifics of how you want handlers to interact. For this code, just changing the type of next handler (in both var nextHandlerInChain
and fun setNextHandler
to ChainHandler<*>
would work, since process()
returns something independent of T
anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With