如何在 Kotlin 中使用类型安全的构建器?



我看过很多教程,但仍然没有确切了解它是如何工作的。我理解了主要思想:一个保存带有数据的函数,但查看官方文档,我无法意识到数据的存储方式和位置以及谁调用负责存储数据的函数。其他教程似乎只显示了一段代码,这对我没有多大帮助。你能给我一个完整而简单的例子,比如一个人,一个琐碎的类吗?

我也对一些细节感兴趣。这是我写的:

data class Person(
var name: String? = null,
var age: Int? = null,
val children: MutableList<Person> = ArrayList()
) {
fun child(init: Person.() -> Unit) = Person().also {
it.init()
children.add(it)
}
}
fun person(init: Person.() -> Unit) = Person().apply { init() }
fun main(args: Array<String>) {
val p = person {
name = "Mommy"
age = 33
child {
name = "Gugu"
age = 2
}
child {
name = "Gaga"
age = 3
}
}
println(p)
}

它打印出来(添加了一些格式):

Person(name=Mommy, age=33, children=[
Person(name=Gugu, age=2, children=[]), 
Person(name=Gaga, age=3, children=[])
])

Kotlin DSL

Kotlin 非常适合编写您自己的域特定语言,也称为类型安全构建器。Anko是使用这种DSL的例子之一。这里需要了解的最重要的语言功能称为"带接收器的函数文字",您已经使用了它:Test.() -> Unit

带接收器的函数文字 - 基础知识

Kotlin 支持"带有接收器的函数文字"的概念。这使我们能够在其主体中调用函数文字的接收者的方法,而无需任何特定的限定符。这与扩展函数非常相似,在扩展函数中,还可以访问扩展内接收器对象的成员。

一个简单的例子,也是 Kotlin 标准库中最伟大的函数之一,是apply

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

如您所见,将带有接收器的此类函数文字作为此处block参数。这个块被简单地执行,接收器(它是T的一个实例)被返回。在操作中,这如下所示:

val foo: Bar = Bar().apply {
color = RED
text = "Foo"
}

我们实例化一个Bar对象并对其调用applyBar的实例成为"接收方"。block,作为参数在{}(lambda表达式)中传递,不需要使用额外的限定符来访问和修改显示的可见属性colortext

带接收器的函数文字 - 在 DSL 中

如果您查看此示例(取自文档),您会看到以下内容:

class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()  // create the receiver object
html.init()        // pass the receiver object to the lambda
return html
}

html {       // lambda with receiver begins here
body()   // calling a method on the receiver object
}

html()函数需要这样一个函数文字,其中接收器具有HTML作为接收器。在函数体中,您可以看到它是如何使用的:创建HTML的实例并在其上调用init

效益

这种高阶函数的调用者期望带有接收器的函数文字(如html()),您可以使用任何可见的HTML函数和属性,而无需额外的限定符(例如this),如您在调用中看到的那样:

html {       // lambda with receiver begins here
body()   // calling a method on the receiver object
}

我写了一个示例DSL,并在博客文章中对其进行了描述。也许这也是有帮助的。

只是为了添加其他语法

data class QCMBean(var qcmId : Int=-1, var question : String = "", var answers : ArrayList<AnswerBean> = ArrayList()) {
companion object {
fun qcm(init:QCMBean.()->Unit) = QCMBean().apply {
init()
}
}
fun answer(answer:String = "") = AnswerBean(answer).apply {
answers.add(this)
}
}
data class AnswerBean(var answer:String = "")

qcm {
qcmId = 1
question = "How many cat ?"
answer("1")
answer("2")
}

最新更新