I have a some library method, which I can't really change.
class Lib {
inline fun <reified T> getSomething(): T {
// ...
}
}
It uses reified generic parameter to pass information about the generic return type. It sadly does not have a version with KClass<T>
instead of reified
.
I would like to make a class that wraps this library's funcionality to simplify the access to it and allows dependency injection so I can mock it and test classes that depend on it. I also have interface Something
that restricts the generic parameter T
in my app.
sealed interface Something {
// ...
}
interface Wrapper {
fun <T : Something> getSomething(): T
}
class LibWrapper : Wrapper {
private val library = Lib()
fun <T : Something> getSomething(): T {
// ... it does some other stuff around as well
val something = library.getSomething<T>()
// ...
}
}
This is obviously not working because the non-reified generics are only relevant at compile time. I tried several approaches unsuccessfuly:
- Propagating inline up into my code
This is not possible because virtual members cannot be inline and also you cannot override non-inline with inline, so no dependency injection.
internal interface Wrapper {
inline fun <reified T : Something> getSomething(): T
}
internal class LibWrapper : Wrapper {
private val library = Lib()
override inline fun <reified T : Something> getSomething(): T {
// ...
val something = library.getSomething<T>()
// ...
}
}
- Using
KClass<T>
This also does not work because T
is still available only at compile time (I guess).
interface Wrapper {
fun <T : Something> getSomething(type: KClass<out T>): T
}
class LibWrapper : Wrapper {
private val library = Lib()
override fun <T : Something> getSomething(type: KClass<out T>): T {
// ...
val something = library.getSomething<T>()
// ...
}
}
So what should I do? I know this is little bit of a duplicate of these to questions #1 #2, but I would like to expand on them by asking about solution for this general problem. I mean, I am sure that author of the library had his reasons for using reified and the answer "it is not possible" doesn't seem good enough, should I not do the dependecny injection then or not use generics at all here?
I have a few ideas:
Let's say I have two classes that implement Something
class A : Something {
// ...
}
class B : Something {
// ...
}
- Do a method for each type implementing
Something
interface
I could wrap A
and B
each into a method of its own.
interface Wrapper {
fun getA(): A
fun getB(): B
}
class LibWrapper : Wrapper {
private val library = Lib()
override fun getA(): A {
// ...
val something = library.getSomething<A>()
// ...
}
override fun getB(): B {
// ...
val something = library.getSomething<B>()
// ...
}
}
This is kinda tedious if the number of implementers of Something
begins to grow and if the Wrapper
methods share common code (u could extract that into another private method tho). I have about 8 methods like this provided by Lib
, so even with having lets say 5 implementers of Something
it pretty quickly grows to about 40 methods, which seems pretty excessive considering that all of them do pretty much the same thing. Also this kinda seems to defeat the whole purpose of generics if I have to implement each method separately.
- Map
KClass<T>
togetSomething<T>()
by hand
I can use when
to make my own version of getSomething
extension method on Lib
that uses KClass<T>
and calls corresponding getSomething
method with concrete type. Then I can use the 2. approach above.
@Suppress("UNCHECKED_CAST")
fun <T : Something> Lib.getSomething(type: KClass<out T>): T {
return when(type) {
A::class -> getSomething<A>() as T
B::class -> getSomething<B>() as T
else -> throw NotImplementedError()
}
}
This shares similar issues as the approach above. It is little bit less code, but is bit more confusing since you have to rememeber to put a new case for each implementer inside this extension. Also it for some reason requires the else
case even though the interface is sealed
so it should be impossible to execute.
- Some entirely different approach/architecture?
Should go about it in an entirely different way? My only constraints are that I would like dependency injection around the library and I (obviously) can't change the library methods. implementers of Something
are @Serializable data class
if it somehow helps.
What approach should I go for?
As was mentioned in the comments, there is really no reason for library not to have the
KClass
overloadIn my case it was just hidden behind
SerializationStrategy
type, so I missed it. I had to passtype.serializer()
instead of justtype
.