【问题标题】:How to create Kotlin DSL - DSL syntax Kotlin如何创建 Kotlin DSL - DSL 语法 Kotlin
【发布时间】:2018-02-17 05:22:36
【问题描述】:

anko 一样,您可以编写如下回调函数:

alert {
    title = ""
    message = ""
    yesButton {
       toast("Yes") 
    }
    noButton { 
       toast("No")
    }
}

如何创建这样的嵌套函数?我尝试像下面这样创建它,但似乎没有用。

class Test {
    fun f1(function: () -> Unit) {}
    fun f2(function: () -> Unit) {}
}

现在,如果我将它与扩展功能一起使用,

fun Context.temp(function: Test.() -> Unit) {
    function.onSuccess() // doesn't work
}

从 Activity 调用这个:

temp {
    onSuccess {
        toast("Hello")
    }
}

不起作用。我在这里仍然缺乏一些基本概念。有人可以在这里指导吗?

【问题讨论】:

标签: android kotlin kotlin-extension anko


【解决方案1】:

Kotlin DSL

Kotlin 非常适合编写自己的领域特定语言,也称为type-safe builders。正如您所提到的,Anko 库是一个使用 DSL 的示例。您需要在这里了解的最重要的语言功能称为"Function Literals with Receiver",您已经使用过它:Test.() -> Unit

带有接收器的函数文字 - 基础

Kotlin 支持“带有接收器的函数文字”的概念。这允许在其主体中调用函数文字的接收器上的可见方法,而无需任何特定的限定符。这非常类似于扩展函数,也可以在扩展函数中访问接收器对象的成员。

一个简单的例子,也是 Kotlin 标准库中最酷的函数之一,isapply

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

如你所见,这样一个带有接收器的函数字面量在这里被当作参数block。这个block 被简单地执行并返回接收者(它是T 的一个实例)。实际情况如下:

val text: String = StringBuilder("Hello ").apply {
            append("Kotliner")
            append("! ")
            append("How are you doing?")
        }.toString()

StringBuilder 用作接收器,并在其上调用 applyblock,作为{}(lambda表达式)中的参数传递,不需要使用额外的限定符,只需多次调用appendStringBuilder的可见方法。

带有接收器的函数文字 - 在 DSL 中

如果您查看此示例,取自文档,您会看到它的实际效果:

class HTML {
    fun body() { ... }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // create the receiver object
    html.init()        // pass the receiver object to the lambda
    return html
}


html {       // lambda with receiver begins here
    body()   // calling a method on the receiver object
}

html() 函数需要这样一个函数字面量,其中接收者为 HTML 作为接收者。在函数体中,您可以看到它是如何使用的:创建HTML 的实例并在其上调用init

好处

这样一个高阶函数的调用者期望一个带有接收者的函数字面量(如html()),您可以使用任何可见的HTML函数和属性而无需额外的限定符(如this例如),正如您在通话中看到的那样:

html {       // lambda with receiver begins here
    body()   // calling a method on the receiver object
}

你的例子

我创建了一个您想要的简单示例:

class Context {
    fun onSuccess(function: OnSuccessAction.() -> Unit) {
        OnSuccessAction().function();
    }

    class OnSuccessAction {
        fun toast(s: String) {
            println("I'm successful <3: $s")
        }
    }
}

fun temp(function: Context.() -> Unit) {
    Context().function()
}

fun main(args: Array<String>) {
    temp {
        onSuccess {
            toast("Hello")
        }
    }
}

【讨论】:

  • 需要一些时间来理解这一点。没有直接进入我的脑海:/
  • 你到底有什么难得到的,那么让我提供更多信息?
  • 感谢您的回复。我晚上看看,然后告诉你
  • 好酷。我稍微扩展了描述。要理解的核心概念是“带有接收器的函数文字”,也就是“带有接收器的 lambda”
  • 谢谢 :) 我去看看
【解决方案2】:

在您的示例中,alert 是返回某个类的函数,例如 Alert。 此函数也将接收器作为参数函数文字

在您的示例中,您应该 onSuccess 您的 Test 类的成员方法,并且您的 temp 函数应该返回 Test 类的实例而不调用它。 但是要按照您的意愿调用 toast,它必须是 onSuccess 返回的任何类的成员函数

我认为您并不完全了解带有接收器的函数式文字是如何工作的。 当你玩得开心的时候(something : A.() -> Unit) 这意味着这个“东西”是A类的成员函数。

所以

你可以看看我的博文:How to make small DSL for AsyncTask

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-05
    • 1970-01-01
    • 2019-09-15
    • 1970-01-01
    • 2019-08-29
    • 1970-01-01
    • 2021-05-07
    • 2019-04-06
    相关资源
    最近更新 更多