Reputation: 13395
I currently have this class with dsl
like building ability
class GRLMessage {
var headerMap : MutableMap<String, String> = mutableMapOf()
lateinit var methodType : GRLMethod
lateinit var multipartObject : IGRLMultipart
fun message(closure: GRLMessage.() -> Unit) : GRLMessage {
closure()
return this
}
fun method(closure: GRLMessage.() -> GRLMethod) : GRLMessage {
methodType = closure()
return this
}
fun headers(closure: GRLMessage.() -> Unit) : GRLMessage {
closure()
return this
}
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
var pair = closure()
headerMap.put(pair.first, pair.second)
return this
}
fun multipart(closure: GRLMessage.() -> IGRLMultipart) : GRLMessage {
multipartObject = closure()
return this
}
}
And I test it like this
class GRLMessageTest {
data class DummyMultipart(val field: String) : IGRLMultipart {
override fun getContent() {
this
}
}
@Test fun grlMessageBuilderTest() {
val grlMessage = GRLMessage().message {
method { GRLMethod.POST }
headers {
header { Pair("contentType", "object") }
header { Pair("objectType", "DummyMultipart") }
}
multipart { DummyMultipart("dummy") }
}
val multipart = DummyMultipart("dummy")
val headers = mapOf(
Pair("contentType", "object"),
Pair("objectType", "DummyMultipart")
)
val method = GRLMethod.POST
assertEquals(multipart, grlMessage.multipartObject)
assertEquals(method, grlMessage.methodType)
assertEquals(headers, grlMessage.headerMap)
}
}
But despite providing
header { Pair("contentType", "object") }
I still have to evaluate closure
inside header
method and directly put
key and value into my MutableMap
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
var pair = closure()
headerMap.put(pair.first, pair.second)
return this
}
Is there a better way adding entries to Map
?
Upvotes: 1
Views: 1263
Reputation: 85946
Adding an extension function makes your fluent methods more obviously fluent:
fun <T: Any> T.fluently(func: ()->Unit): T {
return this.apply { func() }
}
With that your fluent function is always clear about its return:
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
return fluently { headerMap += closure() }
}
Which is really the same as:
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
return this.apply { headerMap += closure() }
}
But the extension function adds a touch of readability.
Above I use the answer given in by @Ruckus for solving your specific question of adding a Pair
to the headerMap
. But you have other options that you might want to know about for other use cases of your DSL...
You can use let
, apply
or with
which would allow any type of decomposition of the results of closure()
call (maybe it is more complicated than Pair
in the future). All of these are basically the same, minus their resulting value:
with(closure()) { headerMap.put(this.first, this.second) }
closure().apply { headerMap.put(this.first, this.second) }
closure().let { headerMap.put(it.first, it.second) }
Using let
or apply
is nice if you want to handle a case where closure()
allows nullable return, in which case you might want to take action only if not null
:
closure()?.apply { headerMap.put(this.first, this.second) }
closure()?.let { headerMap.put(it.first, it.second) }
Other notes about your code:
val
instead of var
unless you have no other choicelateinit
(or the similar Delegates.notNull()
) seem dangerous to use in an uncontrolled lifecycle where there is no guarantee it will be completed, because the error message will be confusing and happen at some unexpected time in the future. There are likely other ways to solve this with a DSL that chains calls to create more of a multi-step grammarYou can shorten code by only having types on one side of the assignment, for example:
val myMap = mutableMapOf<String, String>()
instead of
var myMap : MutableMap<String, String> = mutableMapOf()
Upvotes: 2
Reputation: 4786
Does your headerMap
need to be a var? If not, you can change it to a val and use headerMap += closure()
.
Upvotes: 3
Reputation: 13395
Well for now as a solution I created extension for MutableMap
fun MutableMap<String, String>.put(pair : Pair<String, String>) {
this.put(pair.first, pair.second)
}
Which allowed me to write like this
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
headerMap.put(closure())
return this
}
Upvotes: 0