diesieben07
diesieben07

Reputation: 1557

Kotlin factory interface with generics

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

Answers (1)

diesieben07
diesieben07

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

Related Questions