smallufo
08/08/2021, 8:35 PMclass Config(var degree: Double = 315.0) {
init {
require(degree > 180 && degree < 360)
{ "degree should between 180 and 360" }
}
}
And if we initialize Config directly , it will check the value as expected :
@Test
fun normalTest() {
try {
Config(90.0)
fail()
} catch (e: Exception) {
assertTrue(true)
}
}
But if there is a function accepting the Config as functional object :
private fun calculate(block: Config.() -> Unit = {}) {
val cfg = Config().apply(block)
println("do something... with degree = ${cfg.degree}")
}
@Test
fun testCalculate() {
try {
calculate {
degree = 90.0
}
fail("It SHOULD fail")
} catch (e: Exception) {
assertTrue(true)
}
}
It will skip the init check block .
How to solve this problem ? Thanks.
I could only think of this solution :
class Config(var degree: Double = 315.0) {
init {
check()
}
fun check() {
require(degree > 180 && degree < 360)
{ "degree should between 180 and 360" }
}
}
and …
private fun calculate(block: Config.() -> Unit = {}) {
val cfg = Config().apply(block).also { it.check() }
println("do something... with degree = ${cfg.degree}")
}
But I think it is so ugly … Is there any better way ? Thanks.Ayfri
08/08/2021, 8:59 PMdegree property, in which you put the require in its init function like you didRob Elliot
08/08/2021, 9:03 PMinit is called when the object is constructed. When you call Config().apply(block), the first thing that happens is Config(), with your default value for degree of 315.0. So the init is called, and passes, because 315 is between 180 and 360.
You then set degree to 90 - but you have no guard on changing degree.
Do you really want Config to be mutable? If so, and you want to keep the invariant that degree > 180 && degree < 360, you need to add it to `degree`’s setter.smallufo
08/08/2021, 9:07 PMConfig class.
to @Rob Elliot: Config needs to be mutable , so that client of the function may call calculate like this
calculate {
degree = 90.0
}Rob Elliot
08/08/2021, 9:08 PMConfig(degree = 90.0) and make degree a val?smallufo
08/08/2021, 9:11 PMktor 's config (ex : https://ktor.io/docs/http-client-engines.html#java )
such as :
calculate (obj1 , obj2 ) {
degree = 90.0
angle {
value = 60.0
}
}Rob Elliot
08/08/2021, 9:12 PMclass Config(
degree: Double = 315.0
) {
var degree: Double = degree
set(value) {
require(value > 180 && value < 360)
field = value
}
init {
this.degree = degree
}
}smallufo
08/08/2021, 9:15 PMRob Elliot
08/08/2021, 9:16 PMapply is an extension function you call after the object has been constructed. Basically you pass the constructed Config instance as an argument to apply, so it absolutely has to already have been constructed.Rob Elliot
08/08/2021, 9:19 PMsmallufo
08/08/2021, 9:20 PMRob Elliot
08/08/2021, 9:21 PMConfig().apply { degree = 90 } is exactly the same as:
val config = Config()
config.degree = 90Ayfri
08/08/2021, 9:24 PMapply function ?ephemient
08/08/2021, 10:02 PMclass ConfigBuilder {
var degree: Double = 315.0
}
class Config(val degree: Double)
fun Config(block: ConfigBuilder.() -> Unit): Config {
val builder = ConfigBuilder()
builder.block()
return Config(builder.degrees)
}
then you can
Config { degree = 90.0 }
and a check in Config.init will happen after the block runssmallufo
08/08/2021, 10:39 PM