在Kotlin中,在创建自定义DSL时,在编译时强制填充构建器扩展函数中的必需字段的最佳方法是什么.例如:
person {
name = "John Doe" // this field needs to be set always, or compile error
age = 25
}
Run Code Online (Sandbox Code Playgroud)
强制它的一种方法是在函数参数中设置值,而不是扩展函数的主体.
person(name = "John Doe") {
age = 25
}
Run Code Online (Sandbox Code Playgroud)
但如果有更多必填字段,那就更难以理解了.
还有其他方法吗?
Ban*_*non 11
新的类型推断使您能够创建一个空安全的编译时检查构建器:
data class Person(val name: String, val age: Int?)
// Create a sealed builder class with all the properties that have default values
sealed class PersonBuilder {
var age: Int? = null // `null` can be a default value if the corresponding property of the data class is nullable
// For each property without default value create an interface with this property
interface Named {
var name: String
}
// Create a single private subclass of the sealed class
// Make this subclass implement all the interfaces corresponding to required properties
private class Impl : PersonBuilder(), Named {
override lateinit var name: String // implement required properties with `lateinit` keyword
}
companion object {
// Create a companion object function that returns new instance of the builder
operator fun invoke(): PersonBuilder = Impl()
}
}
// For each required property create an extension setter
fun PersonBuilder.name(name: String) {
contract {
// In the setter contract specify that after setter invocation the builder can be smart-casted to the corresponding interface type
returns() implies (this@name is PersonBuilder.Named)
}
// To set the property, you need to cast the builder to the type of the interface corresponding to the property
// The cast is safe since the only subclass of `sealed class PersonBuilder` implements all such interfaces
(this as PersonBuilder.Named).name = name
}
// Create an extension build function that can only be called on builders that can be smart-casted to all the interfaces corresponding to required properties
// If you forget to put any of these interface into where-clause compiler won't allow you to use corresponding property in the function body
fun <S> S.build(): Person where S : PersonBuilder, S : PersonBuilder.Named = Person(name, age)
Run Code Online (Sandbox Code Playgroud)
用例:
val builder = PersonBuilder() // creation of the builder via `invoke` operator looks like constructor call
builder.age = 25
// builder.build() // doesn't compile because of the receiver type mismatch (builder can't be smart-casted to `PersonBuilder.Named`)
builder.name("John Doe")
val john = builder.build() // compiles (builder is smart-casted to `PersonBuilder & PersonBuilder.Named`)
Run Code Online (Sandbox Code Playgroud)
现在你可以添加一个 DSL 函数:
// Caller must call build() on the last line of the lambda
fun person(init: PersonBuilder.() -> Person) = PersonBuilder().init()
Run Code Online (Sandbox Code Playgroud)
DSL 用例:
person {
name("John Doe") // will not compile without this line
age = 25
build()
}
Run Code Online (Sandbox Code Playgroud)
最后,在 2019 年 JetBrains 开放日,据说 Kotlin 团队研究了合约并试图实施合约,以允许创建具有所需字段的安全 DSL。这是俄语的谈话录音。这个特性甚至不是一个实验性的特性,所以它可能永远不会被添加到语言中。
如果您正在为 Android 进行开发,我编写了一个轻量级 linter 来验证强制 DSL 属性。
要解决您的用例,您只需@DSLMandatory
向name
属性设置器添加注释,linter 将捕获未分配的任何位置并显示错误:
@set:DSLMandatory
var name: String
Run Code Online (Sandbox Code Playgroud)
您可以在这里查看: https: //github.com/hananrh/dslint/
很简单,如果块后面的 DLS 中未定义该异常,则抛出异常
fun person(block: (Person) -> Unit): Person {
val p = Person()
block(p)
if (p.name == null) {
// throw some exception
}
return p
}
Run Code Online (Sandbox Code Playgroud)
或者,如果您想在构建时强制执行它,只需让它在未定义的情况下向外部块返回无用的内容,例如 null。
fun person(block: (Person) -> Unit): Person? {
val p = Person()
block(p)
if (p.name == null) {
return null
}
return p
}
Run Code Online (Sandbox Code Playgroud)
我猜你会离开这个例子,所以也许地址会是更好的例子:
fun Person.address(block: Address.() -> Unit) {
// city is required
var tempAddress = Address().apply(block)
if (tempAddress.city == null) {
// throw here
}
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我们想要确保所有内容都已定义,但又想让您以任意顺序执行并在编译时中断,该怎么办?很简单,有两种!
data class Person(var name: String = null,
var age: Int = null,
var address: Address = null)
data class PersonBuilder(var name: String? = null,
var age: Int? = null,
var address: Address? = null)
fun person(block: (PersonBuilder) -> Unit): Person {
val pb = PersonBuilder()
block(p)
val p = Person(pb.name, pb.age, pb.address)
return p
}
Run Code Online (Sandbox Code Playgroud)
这样,您就可以获得要构建的非严格类型,但最终最好是无空的。这是一个有趣的问题,谢谢。
归档时间: |
|
查看次数: |
418 次 |
最近记录: |