Как создать Kotlin DSL - DSL синтаксис Kotlin
С Анко вы можете написать функции обратного вызова, как это:
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")
}
}
не работает. Мне все еще не хватает некоторых основных понятий. Может кто-нибудь здесь?
2 ответов
Котлин DSL-Языки
Котлин отлично подходит для написания собственных Доменные Языки, также называемый тип-безопасный строителей. Как вы упомянули, библиотека Anko является примером использования DSL. Самая важная языковая функция, которую вам нужно понять, называется "функциональные литералы с приемником", которым вы уже воспользовались:Test.() -> Unit
функциональные литералы с приемником-основы
Котлин поддерживает концепцию "литералов функций с приемниками". Это позволяет вызывать видимые методы на приемник литерала функции в его теле без каких-либо конкретных классификаторов. Это очень похожие на функции расширения, в котором также можно получить доступ к членам объекта receiver внутри расширения.
простой пример, также одна из самых крутых функций в стандартной библиотеке Котлина, isapply
:
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
как вы можете видеть, такой литерал функции с receiver берется в качестве аргумента block
здесь. Это block
просто выполняется и приемник (который является экземпляром T
) возвращается. В действии это выглядит следующим образом:
val text: String = StringBuilder("Hello ").apply {
append("Kotliner")
append("! ")
append("How are you doing?")
}.toString()
A StringBuilder
используется в качестве приемника и apply
вызывается на это. The block
, передано как аргумент в {}
(лямбда-выражение), не нужно использовать дополнительные квалификаторы и просто звонки append
, видимый метод StringBuilder
несколько раз.
функциональные литералы с приемником-в 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")
}
}
}
в вашем примере alert-это функция, возвращающая некоторый класс, например Alert. Также эта функция принимает в качестве литерала функции параметра с receiver
в вашем примере вы должны сделать свой onSuccess метод-член вашего тестового класса, и ваша функция temp должна возвращать экземпляр тестового класса без его вызова. Но чтобы тост вызывался, как в вашем желании, он должен быть функцией-членом любого класса, возвращаемого onSuccess
Я думаю, что вы не поймите, как именно работают функциональные литералы с receiver. Когда вам весело(что-то: A. () - > Unit), это означает, что это "что-то" является функцией-членом класса.
Так
вы можете посмотреть на моем блоге : как сделать небольшой DSL для AsyncTask