類型安全的構(gòu)建器可以創(chuàng)建基于 Kotlin 的適用于采用半聲明方式構(gòu)建復(fù)雜層次數(shù)據(jù)結(jié)構(gòu)領(lǐng)域?qū)S谜Z言(DSL)。 以下是構(gòu)建器的一些示例應(yīng)用場景:
— 使用 Kotlin 代碼生成標(biāo)記語言,例如 HTML 或 XML;
— 以編程方式布局UI組件:Anko;
— 為Web服務(wù)器配置路由:Ktor。
一個類型安全的構(gòu)建器示例
考慮下面的代碼
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" } // 一個具有屬性和文本內(nèi)容的元素 a() { +"Kotlin" } // 混合的內(nèi)容 p { +"This is some" b { +"mixed" } +"text. For more see the" a() { +"Kotlin" } +"project" } p { +"some text" } // 以下代碼生成的內(nèi)容 p { for (arg in args) +arg } } }這是完全合法的 Kotlin 代碼。你可以在這里在線運行上文代碼(修改它并在瀏覽器中運行)
實現(xiàn)原理
讓我們來看看 Kotlin 中實現(xiàn)類型安全構(gòu)建器的機(jī)制。首先,我們需要定義我們想要構(gòu)建的模型,在本例中我們 需要建模 HTML 標(biāo)簽。用一些類就可以輕易完成。例如,HTML 是一個描述 <html> 標(biāo)簽的類,也就是說它定 義了像 <head> 和 <body> 這樣的子標(biāo)簽。(參?下文它的聲明。)
現(xiàn)在,讓我們回想下為什么我們可以在代碼中這樣寫
html { // ...... }html 實際上是一個函數(shù)調(diào)用,它接受一個 lambda 表達(dá)式 作為參數(shù)。該函數(shù)定義如下
fun html(init: HTML.() -> Unit): HTML { val html = HTML() html.init() return html }這個函數(shù)接受一個名為 init 的參數(shù),該參數(shù)本身就是一個函數(shù)。該函數(shù)的類型是 HTML.() -> Unit,它是 一個 帶接收者的函數(shù)類型 。這意味著我們需要向函數(shù)傳遞一個 HTML 類型的實例( 接收者 ),并且我們可以在 函數(shù)內(nèi)部調(diào)用該實例的成員。該接收者可以通過 this 關(guān)鍵字訪問
html { this.head { ...... } this.body { ...... } }(head 和 body 是 HTML 的成員函數(shù)。)
現(xiàn)在,像往常一樣,this 可以省略掉了,我們得到的東西看起來已經(jīng)非常像一個構(gòu)建器了
html { head { ...... } body { ...... } }那么,這個調(diào)用做什么?讓我們看看上面定義的 html 函數(shù)的主體。它創(chuàng)建了一個 HTML 的新實例,然后通過 調(diào)用作為參數(shù)傳入的函數(shù)來初始化它(在我們的示例中,歸結(jié)為在HTML實例上調(diào)用 head 和 body),然后返 回此實例。這正是構(gòu)建器所應(yīng)做的。
HTML 類中的 head 和 body 函數(shù)的定義與 html 類似。唯一的區(qū)別是,它們將構(gòu)建的實例添加到包含 HTML 實例的 children 集合中
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 }實際上這兩個函數(shù)做同樣的事情,所以我們可以有一個泛型版本,initTag
protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T { tag.init() children.add(tag) return tag }所以,現(xiàn)在我們的函數(shù)很簡單
fun head(init: Head.() -> Unit) = initTag(Head(), init) fun body(init: Body.() -> Unit) = initTag(Body(), init)并且我們可以使用它們來構(gòu)建 <head> 和 <body> 標(biāo)簽。
這里要討論的另一件事是如何向標(biāo)簽體中添加文本。在上例中我們這樣寫到
html { head { title {+"XML encoding with Kotlin"} } // ...... }所以基本上,我們只是把一個字符串放進(jìn)一個標(biāo)簽體內(nèi)部,但在它前面有一個小的 +,所以它是一個函數(shù)調(diào)用, 調(diào)用一個前綴 unaryPlus() 操作。該操作實際上是由一個擴(kuò)展函數(shù) unaryPlus() 定義的,該函數(shù)是TagWithText 抽象類(Title 的父類)的成員
operator fun String.unaryPlus() { children.add(TextElement(this)) }所以,在這里前綴 + 所做的事情是把一個字符串包裝到一個 TextElement 實例中,并將其添加到 children 集合中,以使其成為標(biāo)簽樹的一個適當(dāng)?shù)牟糠帧?/p>
所有這些都在上面構(gòu)建器示例頂部導(dǎo)入的包 com.example.html 中定義。在最后一節(jié)中,你可以閱讀這個包 的完整定義
作用域控制:@DslMarke(r 自 1.1 起)
使用 DSL 時,可能會遇到上下文中可以調(diào)用太多函數(shù)的問題。我們可以調(diào)用 lambda 表達(dá)式內(nèi)部每個可用的隱式接收者的方法,因此得到一個不一致的結(jié)果,就像在另一個 head 內(nèi)部的 head 標(biāo)記那樣 html { head { head {} // 應(yīng)該禁止 } // ...... }在這個例子中,必須只有最近層的隱式接收者 this@head 的成員可用;head() 是外部接收者 this@html 的成員,所以調(diào)用它一定是非法的。
為了解決這個問題,在 Kotlin 1.1 中引入了一種控制接收者作用域的特殊機(jī)制。
為了使編譯器開始控制標(biāo)記,我們只是必須用相同的標(biāo)記注解來標(biāo)注在 DSL 中使用的所有接收者的類型。例如,對于 HTML 構(gòu)建器,我們聲明一個注解 @HTMLTagMarker
@DslMarker annotation class HtmlTagMarker如果一個注解類使用 @DslMarker 注解標(biāo)注,那么該注解類稱為 DSL 標(biāo)記。
在我們的 DSL 中,所有標(biāo)簽類都擴(kuò)展了相同的超類 Tag 。只需使用 @HtmlTagMarker 來標(biāo)注超類就足夠了,之后,Kotlin 編譯器會將所有繼承的類視為已標(biāo)注
@HtmlTagMarker abstract class Tag(val name: String) { ...... }我們不必用 @HtmlTagMarker 標(biāo)注 HTML 或 Head 類,因為它們的超類已標(biāo)注過
class HTML() : Tag("html") { ...... } class Head() : Tag("head") { ...... }在添加了這個注解之后,Kotlin 編譯器就知道哪些隱式接收者是同一個 DSL 的一部分,并且只允許調(diào)用最近層 的接收者的成員
html { head { head { } // 錯誤:外部接收者的成員 } // ...... }請注意,仍然可以調(diào)用外部接收者的成員,但是要做到這一點,你必須明確指定這個接收者
html { head { this@html.head { } // 可能 } // ...... }com.example.html 包的完整定義
這就是 com.example.html 包的定義(只有上面例子中使用的元素)。它構(gòu)建一個 HTML 樹。代碼中大量使 用了擴(kuò)展函數(shù)和帶有接收者的 lambda 表達(dá)式。
請注意,@DslMarker 注解在 Kotlin 1.1 起才可用
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 ") } } @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()}> ") for (c in children) { c.render(builder, indent + " ") } builder.append("$indent</$name> ") } 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 }
分享標(biāo)題:kotlin更多語言結(jié)構(gòu)——>類型安全的構(gòu)建器-創(chuàng)新互聯(lián)
轉(zhuǎn)載來源:http://aaarwkj.com/article6/dohcig.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、品牌網(wǎng)站建設(shè)、網(wǎng)站改版、外貿(mào)建站、關(guān)鍵詞優(yōu)化、網(wǎng)站內(nèi)鏈
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容