如何让孩子爱上 Kotlin : DSL (下)

by

Kotlin 传教文

本文向您介绍 Kotlin 语言的一些奇特之处,方便您对 Kotlin 这门语言建立一个简单的概念。

我就开门见山地接着上次的讲了。

Kotlin 的 extension 是个很神奇的东西。在第一篇文章中我就讲到了 extension ,但是 extension 可不仅仅能这样使用。它还有两种更高级的用法。

泛型

没错, extension 可以和泛型一起使用。比如这个方法:

val file: File // 一个 Property ,重载 get 方法
	internal get() {
		val f = File("boyNextDoor") // 新建一个 File 类型的变量
		if (!f.exists()) f.createNewFile() // 如果不存在那么新建一个
		return f // 返回这个文件对象
	}

看起来是不是很臃肿?明明只需要使用一次的临时产生的变量,却由于需要进行一步额外的操作(检查是否存在)而不能通过匿名的形式直接构造后返回。 它让你必须声明一个对应的变量。这曾经让我很苦恼,但是 extension 却解决了这个问题。 配合 Lambda 使用,根本不需要为此增加一个变量。

// 声明
fun File.apply(block: (File) -> Unit): File {
	block(this)
	return this
}
// 调用
val file: File
	internal get() = File("boyNextDoor").apply { f ->
		if (!f.exists()) f.createNewFile()
	}

这里有个令人困惑的地方。block 是一个签名为(File) -> Unit 的 Lambda ,为什么在调用的时候传入了一个 this 呢?这个 this 指的是什么?

还记得第一篇里面讲的 extension 的 JVM 实现吗。 扩展方法实际上是把第一个参数前面再增加了一个参数,它是调用者自己。 这样,在方法内部,上下文就是这个调用者了。 你可以理解为它将全部的自己的方法调用(就是可以写成 this.fuck()这种形式的)全部加上 this ,然后把 this 换成了$reciver,即编译后生成的那个被增加的参数。

为了增加这个 apply 方法的普适性,我们把它写成泛型形式的:

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

注意,这里的 fun <T>在调用的时候根本不需要写成 van.apply<Aniki>(params)的形式,那个尖括号的类型声明是没有必要的。 因为你写了 T.apply ,当你写下 van.apply 的时候,编译器就已经知道,这个 T 应该是 van 的类型。于是就不需要再显式声明了。

基于那个『扩展其实就是把调用者变成第一个参数』的说法,我们可以做一些有趣的事情。

fun <T> T.apply(block: /*区别在这里!*/T.() -> Unit/*签名不一样!*/): T {
	block(this)
	return this
}

把参数中的 Lambda 也写成了 extension 的形式。在调用的时候,传入参数 this ,作为第一个参数。 然后在调用的时候,比如刚刚说的那个检查文件存在的例子,就不需要写带参数的 Lambda 了。上下文直接就是 File 对象了。

val file: File
	internal get() = File("boyNextDoor").apply {
		if (!exists()) createNewFile()
	}

看到了吗?省去了f ->以及两处f.。这时候你可能会问了,这特性有什么卵用?

构造 DSL 啊!

@JvmName("game")
fun game(block: LanguageSystem.() -> Unit) {
	LanguageSystem(block)
}

class LanguageSystem(val block: LanguageSystem.() -> Unit) : Game() {
		override fun onLastInit() {
		super.onLastInit()
		block.invoke(this)
	}

	fun size(width: Int, height: Int) {
		size = Dimension(width, height)
	}

	fun log(s: String) {
		val f = File(logFile)
		if (!f.exists()) f.createNewFile()
		f.appendText("$s\n")
	}

	fun rectangle(block: DSLShapeObject.() -> Unit) {
		val so = DSLShapeObject(ColorResource.西木野真姬, FRectangle(50, 50))
		block(so)
		addObject(so)
	}

	fun oval(block: DSLShapeObject.() -> Unit) {
		val so = DSLShapeObject(ColorResource.西木野真姬, FOval(25.0, 25.0))
		block(so)
		addObject(so)
	}

	fun image(block: ImageObject.() -> Unit) {
		val io = ImageObject(ImageResource.empty())
		block(io)
		addObject(io)
	}

	fun text(block: SimpleText.() -> Unit) {
		val st = SimpleText("", 0.0, 0.0)
		block(st)
		addObject(st)
	}

	fun button(block: SimpleButton.() -> Unit) {
		val sb = SimpleButton("", 0.0, 0.0, 80.0, 30.0)
		block(sb)
		addObject(sb)
	}
}

上面是我的 Frice 引擎的 Kotlin DSL 的一部分。这样声明之后,调用的代码就像这样:

game {
	log("game start!")

	rectangle {
		x = 100.0
		height = 200.0
	}
	oval {
		y = 30.0
	}
}

是不是感觉完全不会编程的人都能看懂这代码!没错,这就是 DSL 的魔力。(想起了曾经被 Rails 洗脑的恐惧,躲墙角抱头蹲)

于是。。。

本垃圾说完了

嘛,就这样啦。欢迎围观 Frice 全家桶:

求 star 求 follow !(°ー°〃)


Tweet this
Top


创建一个 issue 以申请评论
Create an issue to apply for commentary


协议/License

本作品 如何让孩子爱上 Kotlin : DSL (下) 采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,基于 http://ice1000.org/2016/10/17/LearnKotlin7/ 上的作品创作。
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
知识共享许可协议