工作原理

让我们来讨论一下Kotlin中类型安全Builder的实现机制。首先我们需要定义想创建的模块,在这种情况下,我们需要类似HTML的标签。这可以用一连串的类来实现。比如,HTML是一个描述<html>标签的类,该标签定义了一些子元素(比如<head>、<body>)。

html {
    // ...
}

html是一个以lambda表达式作为参数的函数调用。这个函数的定义如下:

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}

这个函数使用一个叫init的参数,其本身就是一个函数。函数的类型就是HTML.() -> Unit,这是一个带接收器的函数类型。这意味着我们需要传递一个HTML的实例给这个函数,并且我们可以在函数内部访问实例成员。接收器可以使用this关键字进行访问。

html {
    this.head { /* ... */ }
    this.body { /* ... */ }
}

head和body是HTML的成员函数。

this是可以省略的,通常,我们的书写方式更像一个Builder样式:

html {
    head { /* ... */ }
    body { /* ... */ }
}

那么,这个函数调用做了什么事情呢?我们继续查看上述html函数的定义。它创建了一个HTML的实例,然后调用作为参数传递过来的函数(init参数)进行初始化(在我们的示例中,这归结于调用HTML实例的head和body),然后返回这个实例,这就是一个Builder模式所做的操作。

在HTML类中head和body函数的定义与html函数很像。唯一的区别就是它们创建子节点集合到HTML实例中:

fun head(init: Head.() -> Unit) : Head {
    val head = Head()
    head.init()
    children.add(head)
    return head
}
fun body(init: Body.() -> Unit) : Body {
    val body = Body()
    body.init()
    children.add(body)
    return body
}

实际上上述的两个函数做的事情是一样的,因此我们可以使用泛型,initTag:

protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
    tag.init()
    children.add(tag)
    return tag
}

因此,到目前为止,我们的函数很简洁:

fun head(init: Head.() -> Unit) = initTag(Head(), init)
fun body(init: Body.() -> Unit) = initTag(Body(), init)

因此我们可以使用它们来创建<head>和<body>标签。

另一个需要讨论的事情就是添加文本到标签主体。

html {
    head {
        title {+"XML encoding with Kotlin"}
    }
    // ...
}

我们仅仅是在标签主体添加了一个字符串,但是在内容前面有个+,因此这是一个函数调用,实际调用的是unaryPlus()操作。这个操作被定义在unaryPlus()扩展函数中,是TagWithText抽象类的一个成员(Title的父类)。

fun String.unaryPlus() {
    children.add(TextElement(this))
}

因此这里的+将字符串包装到了一个TextElement的实例中,并且将其添加到了children集合中。

所有的这一切都在com.example.html包中定义了。在最后一个章节你可以查看所有的包定义细节。

results matching ""

    No results matching ""