<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 類型安全的構建器 [TOC] 通過使用命名得當的函數作為構建器,結合[帶有接收者的函數字面值](http://www.kotlincn.net/docs/reference/lambdas.html#%E5%B8%A6%E6%9C%89%E6%8E%A5%E6%94%B6%E8%80%85%E7%9A%84%E5%87%BD%E6%95%B0%E5%AD%97%E9%9D%A2%E5%80%BC),可以在 Kotlin 中創建類型安全、靜態類型的構建器。 類型安全的構建器可以創建基于 Kotlin 的適用于采用半聲明方式構建復雜層次數據結構領域專用語言(DSL)。以下是構建器的一些示例應用場景: * 使用 Kotlin 代碼生成標記語言,例如 [HTML](https://github.com/Kotlin/kotlinx.html) 或 XML; * 以編程方式布局 UI 組件:[Anko](https://github.com/Kotlin/anko/wiki/Anko-Layouts); * 為 Web 服務器配置路由:[Ktor](https://ktor.kotlincn.net/features/routing.html#routing-tree)。 ## 一個類型安全的構建器示例 考慮下面的代碼: ```kotlin import com.example.html.* // 參見下文聲明 fun result() = html { head { title {+"XML encoding with Kotlin"} } body { h1 {+"XML encoding with Kotlin"} p {+"this format can be used as an alternative markup to XML"} // 一個具有屬性和文本內容的元素 a(href = "http://kotlinlang.org") {+"Kotlin"} // 混合的內容 p { +"This is some" b {+"mixed"} +"text. For more see the" a(href = "http://kotlinlang.org") {+"Kotlin"} +"project" } p {+"some text"} // 以下代碼生成的內容 p { for (arg in args) +arg } } } ``` 這是完全合法的 Kotlin 代碼。你可以[在這里](https://play.kotlinlang.org/byExample/09_Kotlin_JS/06_HtmlBuilder)在線運行上文代碼(修改它并在瀏覽器中運行)。 ## 實現原理 讓我們來看看 Kotlin 中實現類型安全構建器的機制。首先,我們需要定義我們想要構建的模型,在本例中我們需要建模 HTML 標簽。用一些類就可以輕易完成。例如,`HTML` 是一個描述 `<html>` 標簽的類,也就是說它定義了像 `<head>` 和 `<body>` 這樣的子標簽。(參見[下文](http://www.kotlincn.net/docs/reference/type-safe-builders.html#comexamplehtml-%E5%8C%85%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9A%E4%B9%89)它的聲明。) 現在,讓我們回想下為什么我們可以在代碼中這樣寫: ```kotlin html { // …… } ``` `html` 實際上是一個函數調用,它接受一個 [lambda 表達式](http://www.kotlincn.net/docs/reference/lambdas.html) 作為參數。該函數定義如下: ```kotlin fun html(init: HTML.() -> Unit): HTML { val html = HTML() html.init() return html } ``` 這個函數接受一個名為 `init` 的參數,該參數本身就是一個函數。該函數的類型是 `HTML.() -> Unit`,它是一個 _帶接收者的函數類型_ 。這意味著我們需要向函數傳遞一個 HTML 類型的實例( _接收者_ ),并且我們可以在函數內部調用該實例的成員。該接收者可以通過 *this*關鍵字訪問: ```kotlin html { this.head { …… } this.body { …… } } ``` (`head` 和 `body` 是 `HTML` 的成員函數。) 現在,像往常一樣,*this* 可以省略掉了,我們得到的東西看起來已經非常像一個構建器了: ```kotlin html { head { …… } body { …… } } ``` 那么,這個調用做什么? 讓我們看看上面定義的 `html` 函數的主體。它創建了一個 `HTML` 的新實例,然后通過調用作為參數傳入的函數來初始化它(在我們的示例中,歸結為在HTML實例上調用 `head` 和 `body`),然后返回此實例。這正是構建器所應做的。`HTML` 類中的 `head` 和 `body` 函數的定義與 `html` 類似。唯一的區別是,它們將構建的實例添加到包含 `HTML` 實例的 `children` 集合中: ```kotlin 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`: ```kotlin protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T { tag.init() children.add(tag) return tag } ``` 所以,現在我們的函數很簡單: ```kotlin fun head(init: Head.() -> Unit) = initTag(Head(), init) fun body(init: Body.() -> Unit) = initTag(Body(), init) ``` 并且可以使用它們來構建 `<head>` 和 `<body>` 標簽。 這里要討論的另一件事是如何向標簽體中添加文本。在上例中我們這樣寫到: ```kotlin html { head { title {+"XML encoding with Kotlin"} } // …… } ``` 所以基本上,我們只是把一個字符串放進一個標簽體內部,但在它前面有一個小的 `+`,所以它是一個函數調用,調用一個前綴 `unaryPlus()` 操作。該操作實際上是由一個擴展函數 `unaryPlus()` 定義的,該函數是 `TagWithText` 抽象類(`Title` 的父類)的成員: ```kotlin operator fun String.unaryPlus() { children.add(TextElement(this)) } ``` 所以,在這里前綴 `+` 所做的事情是把一個字符串包裝到一個 `TextElement` 實例中,并將其添加到 `children` 集合中,以使其成為標簽樹的一個適當的部分。 所有這些都在上面構建器示例頂部導入的包 `com.example.html` 中定義。在最后一節中,你可以閱讀這個包的完整定義。 ## 作用域控制:@DslMarker(自 1.1 起) 使用 DSL 時,可能會遇到上下文中可以調用太多函數的問題。我們可以調用 lambda 表達式內部每個可用的隱式接收者的方法,因此得到一個不一致的結果,就像在另一個 `head` 內部的 `head` 標記那樣: ```kotlin html { head { head {} // 應該禁止 } // …… } ``` 在這個例子中,必須只有最近層的隱式接收者 `this@head` 的成員可用;`head()` 是外部接收者 `this@html` 的成員,所以調用它一定是非法的。 為了解決這個問題,在 Kotlin 1.1 中引入了一種控制接收者作用域的特殊機制。 為了使編譯器開始控制標記,我們只是必須用相同的標記注解來標注在 DSL 中使用的所有接收者的類型。例如,對于 HTML 構建器,我們聲明一個注解 `@HTMLTagMarker`: ```kotlin @DslMarker annotation class HtmlTagMarker ``` 如果一個注解類使用 `@DslMarker` 注解標注,那么該注解類稱為 DSL 標記。 在我們的 DSL 中,所有標簽類都擴展了相同的超類 `Tag`。只需使用 `@HtmlTagMarker` 來標注超類就足夠了,之后,Kotlin 編譯器會將所有繼承的類視為已標注: ```kotlin @HtmlTagMarker abstract class Tag(val name: String) { …… } ``` 我們不必用 `@HtmlTagMarker` 標注 `HTML` 或 `Head` 類,因為它們的超類已標注過: ``` class HTML() : Tag("html") { …… } class Head() : Tag("head") { …… } ``` 在添加了這個注解之后,Kotlin 編譯器就知道哪些隱式接收者是同一個 DSL 的一部分,并且只允許調用最近層的接收者的成員: ```kotlin html { head { head { } // 錯誤:外部接收者的成員 } // …… } ``` 請注意,仍然可以調用外部接收者的成員,但是要做到這一點,你必須明確指定這個接收者: ```kotlin html { head { this@html.head { } // 可能 } // …… } ``` ## `com.example.html` 包的完整定義 這就是 `com.example.html` 包的定義(只有上面例子中使用的元素)。它構建一個 HTML 樹。代碼中大量使用了[擴展函數](http://www.kotlincn.net/docs/reference/extensions.html)和[帶有接收者的 lambda 表達式](http://www.kotlincn.net/docs/reference/lambdas.html#%E5%B8%A6%E6%9C%89%E6%8E%A5%E6%94%B6%E8%80%85%E7%9A%84%E5%87%BD%E6%95%B0%E5%AD%97%E9%9D%A2%E5%80%BC)。 請注意,`@DslMarker` 注解在 Kotlin 1.1 起才可用。 ```kotlin package com.example.html interface Element { fun render(builder: StringBuilder, indent: String) } class TextElement(val text: String) : Element { override fun render(builder: StringBuilder, indent: String) { builder.append("$indent$text\n") } } @DslMarker annotation class HtmlTagMarker @HtmlTagMarker abstract class Tag(val name: String) : Element { val children = arrayListOf<Element>() val attributes = hashMapOf<String, String>() protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T { tag.init() children.add(tag) return tag } override fun render(builder: StringBuilder, indent: String) { builder.append("$indent<$name${renderAttributes()}>\n") for (c in children) { c.render(builder, indent + " ") } builder.append("$indent</$name>\n") } private fun renderAttributes(): String { val builder = StringBuilder() for ((attr, value) in attributes) { builder.append(" $attr=\"$value\"") } return builder.toString() } override fun toString(): String { val builder = StringBuilder() render(builder, "") return builder.toString() } } abstract class TagWithText(name: String) : Tag(name) { operator fun String.unaryPlus() { children.add(TextElement(this)) } } class HTML : TagWithText("html") { fun head(init: Head.() -> Unit) = initTag(Head(), init) fun body(init: Body.() -> Unit) = initTag(Body(), init) } class Head : TagWithText("head") { fun title(init: Title.() -> Unit) = initTag(Title(), init) } class Title : TagWithText("title") abstract class BodyTag(name: String) : TagWithText(name) { fun b(init: B.() -> Unit) = initTag(B(), init) fun p(init: P.() -> Unit) = initTag(P(), init) fun h1(init: H1.() -> Unit) = initTag(H1(), init) fun a(href: String, init: A.() -> Unit) { val a = initTag(A(), init) a.href = href } } class Body : BodyTag("body") class B : BodyTag("b") class P : BodyTag("p") class H1 : BodyTag("h1") class A : BodyTag("a") { var href: String get() = attributes["href"]!! set(value) { attributes["href"] = value } } fun html(init: HTML.() -> Unit): HTML { val html = HTML() html.init() return html } ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看