《Kotlin 程序设计》第十一章 Kotlin实现DSL
第十一章 Kotlin实现DSL
正式上架:《Kotlin极简教程》Official on shelves: Kotlin Programming minimalist tutorial
京东JD:https://item.jd.com/12181725.html
天猫Tmall:https://detail.tmall.com/item.htm?id=558540170670
DSL
DSL 即 domain-specific languages,领域特定语言。和一般的编程语言不同,领域特定语言只能用于特定的领域中并且表现形式有限。领域特定语言最大的功能就是可以让语言本身更容易阅读,方便开发者和领域专家进行交流。
实现 DSL
Java 中 DSL 的最简单实现方式就是构造器模式,而在 Kotlin 过去的版本中可以省略 .,所以可以写成更易读的代码,但是现在的版本已经不支持了。
构造器模式
Machine machine = new Machine.Builder()
.setCore(8)
.setArch("64 bits")
.setOs("Linux")
.build();
DSL 方式
定义必要的类和方法
data class Cpu(val core: Int, val arch: String)
class Machine {
var cpu: Cpu? = null
var os: String? = null
fun having(cores: Int, arch: String): Machine {
cpu = Cpu(cores, arch)
return this
}
fun os(os: String): Machine {
this.os = os
return this
}
override fun toString(): String {
return "Machine{cpu=$cpu, os='$os'"
}
}
构建对象
val m1 = Machine().having(8, "64 bits").os("linux")
val m2 = Machine().having(4, "32 bits").os("Windows")
可以看到使用 DSL 后代码更加易读。
使用闭包构建 DSL
Kotlin 像 Groovy 一样也能通过闭包构建 DSL,语法看起来很像 Groovy。
例
定义必要的类和方法
class EmailSpec {
fun from(from: String) = println("From: $from")
fun to(to: String) = println("To: $to")
fun subject(subject: String) = println("Subject: $subject")
fun body(init: BodySpec.() -> Unit): BodySpec {
val body = BodySpec()
body.init()
return body
}
}
class BodySpec {
fun p(p: String) = println("P: $p")
}
fun email(init: EmailSpec.() -> Unit): EmailSpec {
val email = EmailSpec()
email.init()
return email
}
调用 DSL 语句
email {
from ("dsl-guru@mycompany.com")
to ("john.doe@waitaminute.com")
subject ("The pope has resigned!")
body {
p ("Really, the pope has resigned!")
}
}
val data = mapOf(1 to "one", 2 to "two")
createHTML().table {
//遍历数据
for ((num, string) in data) {
//创建 HTML 标签的函数
tr {
td { +"$num" }
td { +string }
}
}
}
/**
* This is an example of a Type-Safe Groovy-style Builder
*
* Builders are good for declaratively describing data in your code.
* In this example we show how to describe an HTML page in Kotlin.
*
* See this page for details:
* http://kotlinlang.org/docs/reference/type-safe-builders.html
*/
package html
fun main(args: Array<String>) {
val 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" }
// an element with attributes and text content
a(href = "http://jetbrains.com/kotlin") { +"Kotlin" }
// mixed content
p {
+"This is some"
b { +"mixed" }
+"text. For more see the"
a(href = "http://jetbrains.com/kotlin") { +"Kotlin" }
+"project"
}
p { +"some text" }
// content generated from command-line arguments
p {
+"Command line arguments were:"
ul {
for (arg in args)
li { +arg }
}
}
}
}
println(result)
}
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")
}
}
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 (a in attributes.keys) {
builder.append(" $a=\"${attributes[a]}\"")
}
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 ul(init: UL.() -> Unit) = initTag(UL(), init)
fun a(href: String, init: A.() -> Unit) {
val a = initTag(A(), init)
a.href = href
}
}
class Body() : BodyTag("body")
class UL() : BodyTag("ul") {
fun li(init: LI.() -> Unit) = initTag(LI(), init)
}
class B() : BodyTag("b")
class LI() : BodyTag("li")
class P() : BodyTag("p")
class H1() : BodyTag("h1")
class A() : BodyTag("a") {
public var href: String
get() = attributes["href"]!!
set(value) {
attributes["href"] = value
}
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html
}