What if I am only interested in onSee
and do not care for other events? Can I at least omit the methods that have no return values?
interface Ev
I came up with the following, somewhat p̶e̶r̶v̶e̶r̶s̶e̶ interesting approach.
The function below uses a dynamic proxy to "materialize" an interface and patch it with only the needed methods. Methods that are not patched will just return null
or Unit
, depending on the return type.
import java.lang.reflect.Proxy.newProxyInstance
inline fun <reified T> Any.materialize(): T = materialize(T::class.java, this)
fun <T> materialize(i: Class<T>, x: Any = object {}): T {
@Suppress("UNCHECKED_CAST")
return newProxyInstance(i.classLoader, arrayOf(i)) { _, m, args ->
x.javaClass.methods
.asSequence()
.filter {
it.name == m.name
&& it.parameterTypes!!.contentEquals(m.parameterTypes)
}
.map {
it.invoke(x, *args.orEmpty())
}.firstOrNull()
} as T
}
It can then be used as follow, given an interface Foo
and an anonymous object that contains only an implementation of its qux()
function:
interface Foo {
fun bar()
fun baz(): String
fun qux(s: String): String
}
fun main(vararg args: String) {
val foo = object {
fun qux(s: String): String {
return "Returned from qux: $s"
}
}.materialize<Foo>()
println(foo.bar()) // void no-op, prints "kotlin.Unit"
println(foo.baz()) // no-op with return value, prints "null"
println(foo.qux("Hello")) // prints "Returned from qux: Hello"
}
Disclaimer
- Using this, you lose all compile-time checking as everything is resolved at runtime.
- Some things are not covered by this implementation (e.g. interface default methods).
- Performance is not taken into account at all.
- Requires the
kotlin-reflect
dependency.- Today is my second day learning Kotlin, so there might be any number of unaddressed edge cases and bugs.
I would not use this myself in most cases, and will continue to hold out for a Kotlin construct that supports partial interface implementations (like TypeScript's
Partial<T>
).I'm only providing this approach because it might be of interest for some use cases, and I'm sorry if this made your eyes bleed.
Sure you can only implement one interface method, all you have to do is to provide a default implementation for the other methods in the interface declaration
interface EventHandler {
fun onSee()
fun onHear() { /* default implementation */ }
fun onSmell(){ /* default implementation */ }
fun onTouch(){ /* default implementation */ }
fun onAwake(){ /* default implementation */ }
fun onSleep(){ /* default implementation */ }
}
Now when you create an instance of this interface you only need to provide a compulsory implementation for onSee()
method, rest are optional
If you're not the author of the original interface You could extend the original interface and provide a default implementation for the methods you want
interface OnSeeEventHandler: EventHandler {
override fun onHear() { /* default implementation */ }
override fun onSmell(){ /* default implementation */ }
override fun onTouch(){ /* default implementation */ }
override fun onAwake(){ /* default implementation */ }
override fun onSleep(){ /* default implementation */ }
}
And use the OnSeeEventHandler
to provide only onSee
method imeplementation