Kotlin.receivers to limit the use of a method
Suppose you are writing a client for some underlying technology (e.i. a database) and you want to add some behavior before or after a method call (like a template method pattern). So, you can have something like this:
class SomeService(private val impl: ServiceImpl) {
fun something(param: String): Int {
otherThing(param)
return impl.doSomething(param)
}
private fun otherThing(param: String): Unit = TODO()
}
interface ServiceImpl {
fun doSomething(param: String): Int
}
The problem is that the methods SomeService.something
and ServiceImpl.doSomething
have the same signature and the question “Why do I need to use the SomeService
if I already had the ServiceImpl
?” will be asked. You can try to document this architecture or evangelize your client’s users. However, is it possible to change the signature of the ServiceImpl.doSomething
to communicate that it belongs to the SomeService
?
Let’s try these two approaches:
Option 1:
class SomeService(private val impl: ServiceImpl2) {
fun something(param: String): Int {
otherThing(param)
return impl.doSomething(param)()
}
private fun otherThing(param: String): Unit = TODO()
}
interface ServiceImpl2 {
fun doSomething(param: String): SomeService.() -> Int
}
This way, the ServiceImpl2.doSomething
returns a function with SomeService
as the receiver, then only an instance of SomeService
can execute it, the drawback is that in the method SomeService.something
needs to add the ()
to execute the actual function.
Option 2:
class SomeService(private val impl: ServiceImpl3) {
fun something(param: String): Int {
otherThing(param)
return (impl.doSomething)(param)
}
private fun otherThing(param: String): Unit = TODO()
}
interface ServiceImpl3 {
val doSomething: SomeService3.(String) -> Int
}
In this case, the function is defined as a value with the parameters embedded in the resulting function. This has two drawbacks, the first one being that the parameters lose its names and make it harder to understand the signature form the ServiceImpl3
viewpoint, and the second being that, by the precedence of kotlin, it is necessary to wrap the call to the impl.doSomething
value.
Conclusion
Both options have pros and cons and I’m not even sure if this signature is good at communicating the use of the client, not to mention the bytecode generated by that signature. But it’s something that the language allows me to do and I want to know your opinions about it.