Reputation: 1557
I have a factory interface in my Kotlin code, like so (using Guava's TypeToken
):
interface ResultMapperFactory {
fun <T> get(type: TypeToken<T>, context: MapperLookupContext): ResultMapper<T>?
}
So far, so good. This is very easy to use when calling it, however implementation almost always requires an unsafe cast:
object StringFactory : ResultMapperFactory {
override fun <T> get(type: TypeToken<T>, context: MapperLookupContext): ResultMapper<T>? {
return if (type.rawType == String::class.java) MyStringMapper as ResultMapper<T> else null
}
}
This is ugly, but I have overcome it with a nice trick. First, a little utility function for creating TypeToken
instances:
inline fun <reified T> typeToken(): TypeToken<T> = object : TypeToken<T>() {}
Now I added a companion object to my factory interface:
companion object {
inline operator fun <reified R> invoke(crossinline body: (TypeToken<R>, MapperLookupContext) -> ResultMapper<R>?): ResultMapperFactory {
val t = typeToken<R>().type
return object : ResultMapperFactory {
override fun <T> get(type: TypeToken<T>, context: MapperLookupContext): ResultMapper<T>? {
return if (type.isSubtypeOf(t)) body(type as TypeToken<R>, context) as ResultMapper<T>? else null
}
}
}
}
This allows me to have the unchecked (but safe) cast in one place and I can write code like the following:
val stringMapper: ResultMapper<String> = TODO()
val stringMapperFactory = ResultMapperFactory<String> { type, context ->
stringMapper
}
However this approach also falls apart as soon as I want to implement a factory that takes a TypeToken<List<T>>
and returns a ResultMapper<List<T>>
, because there is nowhere for me to put that T
parameter.
I am eager to hear your suggestions.
Upvotes: 3
Views: 2018
Reputation: 1557
I found the solution myself, using the invoke
operator fun I posted in my original question I can write the following:
private class ListResultMapper<out E>(private val elementMapper: ResultMapper<E>) : ResultMapper<List<E>> { /* ... */ }
val listMapperFactory = ResultMapperFactory<List<*>> { type, context -> context.resultMapperFactory.get(type.elementType(), context)?.let { ListResultMapper(it) } }
Upvotes: 2