KotlinDSL模式Skill KotlinDSLPatterns

这个技能教授如何在Kotlin中使用领域特定语言设计模式,包括类型安全构建器、lambda接收器、中缀函数、操作符重载等,以创建表达性强、可读性高的DSL,用于配置和领域建模,提高代码可读性、减少样板代码并提供类型安全的API。关键词:Kotlin, DSL, 类型安全, 构建器, lambda接收器, 中缀函数, 操作符重载, 软件开发, API设计。

架构设计 0 次安装 0 次浏览 更新于 3/25/2026

name: Kotlin DSL 模式 user-invocable: false description: 使用Kotlin中的领域特定语言设计模式,包括类型安全构建器、中缀函数、操作符重载、带有接收器的lambda以及用于创建表达性强、可读性高的DSL模式,用于配置和领域建模。 allowed-tools: []

Kotlin DSL 模式

简介

Kotlin的语言特性使得能够创建表达性强的领域特定语言(DSLs),感觉像语言本身的自然扩展。DSLs提高代码可读性,减少样板代码,并为配置、构建器和领域建模提供类型安全的API。

支持DSL设计的关键特性包括带有接收器的lambda、扩展函数、中缀表示法、操作符重载和范围控制。这些特性结合创建流畅、直观的API,清晰地表达领域概念,而不牺牲类型安全或IDE支持。

本技能涵盖类型安全构建器、lambda接收器、中缀函数、操作符重载以及用于在Android、测试和配置上下文中设计可维护DSL的实用模式。

类型安全构建器

类型安全构建器使用带有接收器的lambda来创建层次结构,具有编译时验证和IDE支持。

// HTML DSL 示例
class HTML {
    private val elements = mutableListOf<Element>()

    fun head(init: Head.() -> Unit) {
        val head = Head()
        head.init()
        elements.add(head)
    }

    fun body(init: Body.() -> Unit) {
        val body = Body()
        body.init()
        elements.add(body)
    }

    override fun toString(): String {
        return "<html>
${elements.joinToString("
")}
</html>"
    }
}

abstract class Element(val name: String) {
    private val children = mutableListOf<Element>()

    protected fun <T : Element> initElement(element: T, init: T.() -> Unit): T {
        element.init()
        children.add(element)
        return element
    }

    override fun toString(): String {
        return if (children.isEmpty()) {
            "<$name/>"
        } else {
            "<$name>
${children.joinToString("
")}
</$name>"
        }
    }
}

class Head : Element("head") {
    fun title(text: String) {
        initElement(Title()) { this.text = text }
    }
}

class Title : Element("title") {
    var text: String = ""

    override fun toString() = "<title>$text</title>"
}

class Body : Element("body") {
    fun h1(text: String) {
        initElement(H1()) { this.text = text }
    }

    fun p(text: String) {
        initElement(P()) { this.text = text }
    }

    fun div(cssClass: String = "", init: Div.() -> Unit) {
        initElement(Div(cssClass), init)
    }
}

class H1 : Element("h1") {
    var text: String = ""
    override fun toString() = "<h1>$text</h1>"
}

class P : Element("p") {
    var text: String = ""
    override fun toString() = "<p>$text</p>"
}

class Div(private val cssClass: String = "") : Element("div") {
    fun p(text: String) {
        initElement(P()) { this.text = text }
    }

    override fun toString(): String {
        val classAttr = if (cssClass.isNotEmpty()) " class=\"$cssClass\"" else ""
        return "<div$classAttr>...</div>"
    }
}

// 使用HTML DSL
fun buildPage() = html {
    head {
        title("我的页面")
    }
    body {
        h1("欢迎")
        p("这是一个段落")
        div("container") {
            p("嵌套段落")
        }
    }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}

// 配置DSL
class ServerConfig {
    var port: Int = 8080
    var host: String = "localhost"
    val routes = mutableListOf<Route>()

    fun route(path: String, init: Route.() -> Unit) {
        val route = Route(path)
        route.init()
        routes.add(route)
    }
}

class Route(val path: String) {
    var method: String = "GET"
    var handler: (Request) -> Response = { Response(200, "OK") }

    fun get(handler: (Request) -> Response) {
        this.method = "GET"
        this.handler = handler
    }

    fun post(handler: (Request) -> Response) {
        this.method = "POST"
        this.handler = handler
    }
}

data class Request(val path: String, val body: String = "")
data class Response(val status: Int, val body: String)

fun server(init: ServerConfig.() -> Unit): ServerConfig {
    val config = ServerConfig()
    config.init()
    return config
}

// 使用配置DSL
val config = server {
    port = 9000
    host = "0.0.0.0"

    route("/api/users") {
        get { request ->
            Response(200, "用户列表")
        }
    }

    route("/api/posts") {
        post { request ->
            Response(201, "帖子已创建")
        }
    }
}

类型安全构建器提供IDE自动完成和编译时验证,同时创建可读的层次结构。

带有接收器的Lambda

带有接收器的lambda使DSL函数能够直接访问接收器属性和方法,为更干净的API创建隐式上下文。

// 带有接收器的lambda基础
fun buildString(action: StringBuilder.() -> Unit): String {
    val builder = StringBuilder()
    builder.action()
    return builder.toString()
}

val result = buildString {
    append("你好")
    append(" ")
    append("世界")
}

// 扩展函数作为DSL构建器
class Query {
    private val conditions = mutableListOf<String>()

    fun where(condition: String) {
        conditions.add(condition)
    }

    fun build(): String {
        return "SELECT * WHERE ${conditions.joinToString(" AND ")}"
    }
}

fun query(init: Query.() -> Unit): String {
    val query = Query()
    query.init()
    return query.build()
}

val sql = query {
    where("age > 18")
    where("status = 'active'")
}

// 范围构建器
class TestSuite(val name: String) {
    private val tests = mutableListOf<Test>()

    fun test(name: String, block: TestContext.() -> Unit) {
        val context = TestContext()
        context.block()
        tests.add(Test(name, context))
    }

    fun run() {
        println("运行测试套件: $name")
        tests.forEach { it.run() }
    }
}

class TestContext {
    val assertions = mutableListOf<() -> Unit>()

    fun assertEquals(expected: Any, actual: Any) {
        assertions.add {
            if (expected != actual) {
                throw AssertionError("预期 $expected 但得到 $actual")
            }
        }
    }
}

class Test(val name: String, val context: TestContext) {
    fun run() {
        println("  测试: $name")
        context.assertions.forEach { it() }
    }
}

fun suite(name: String, init: TestSuite.() -> Unit): TestSuite {
    val suite = TestSuite(name)
    suite.init()
    return suite
}

// 使用测试DSL
val testSuite = suite("数学测试") {
    test("加法") {
        assertEquals(4, 2 + 2)
        assertEquals(0, 1 - 1)
    }

    test("乘法") {
        assertEquals(6, 2 * 3)
    }
}

// Apply 和 also 用于DSL链
数据类 Person(
    var name: String = "",
    var age: Int = 0,
    var email: String = ""
)

fun createPerson() = Person().apply {
    name = "Alice"
    age = 30
    email = "alice@example.com"
}

// With 用于范围访问
fun processConfig(config: ServerConfig) {
    with(config) {
        println("服务器在 $host:$port")
        routes.forEach { route ->
            println("  ${route.method} ${route.path}")
        }
    }
}

带有接收器的lambda允许无需显式限定符访问接收器成员,创建自然、上下文感知的DSL语法。

中缀函数和操作符

中缀函数和操作符重载使DSL中能够使用自然数学和逻辑表达式,提高领域概念的可读性。

// 中缀函数用于流畅API
infix fun String.shouldEqual(expected: String) {
    if (this != expected) {
        throw AssertionError("预期 '$expected' 但得到 '$this'")
    }
}

"hello" shouldEqual "hello"

// 时间持续时间DSL与中缀
类 Duration(val milliseconds: Long) {
    operator fun plus(other: Duration) =
        Duration(milliseconds + other.milliseconds)

    override fun toString() = "${milliseconds}ms"
}

infix fun Int.seconds(unit: Unit) = Duration(this * 1000L)
infix fun Int.minutes(unit: Unit) = Duration(this * 60 * 1000L)

对象 Unit

val timeout = 5 seconds Unit
val interval = 2 minutes Unit

// 查询DSL与中缀
类 Condition(val field: String, val operator: String, val value: Any)

infix fun String.eq(value: Any) = Condition(this, "=", value)
infix fun String.gt(value: Any) = Condition(this, ">", value)
infix fun String.lt(value: Any) = Condition(this, "<", value)

类 QueryBuilder {
    private val conditions = mutableListOf<Condition>()

    fun where(condition: Condition) {
        conditions.add(condition)
    }

    infix fun Condition.and(other: Condition): List<Condition> {
        return listOf(this, other)
    }

    fun build(): String {
        return "WHERE ${conditions.joinToString(" AND ") {
            "${it.field} ${it.operator} ${it.value}"
        }}"
    }
}

fun queryBuilder(init: QueryBuilder.() -> Unit): String {
    val builder = QueryBuilder()
    builder.init()
    return builder.build()
}

val query1 = queryBuilder {
    where("age" gt 18)
    where("status" eq "active")
}

// 操作符重载用于DSL
数据类 Vector(val x: Double, val y: Double) {
    operator fun plus(other: Vector) =
        Vector(x + other.x, y + other.y)

    operator fun times(scalar: Double) =
        Vector(x * scalar, y * scalar)

    operator fun unaryMinus() =
        Vector(-x, -y)
}

val v1 = Vector(1.0, 2.0)
val v2 = Vector(3.0, 4.0)
val v3 = v1 + v2
val v4 = v1 * 2.0
val v5 = -v1

// 调用操作符用于类似函数的对象
类 Router {
    private val routes = mutableMapOf<String, (Request) -> Response>()

    operator fun invoke(path: String, handler: (Request) -> Response) {
        routes[path] = handler
    }

    fun handle(request: Request): Response {
        return routes[request.path]?.invoke(request)
            ?: Response(404, "未找到")
    }
}

val router = Router()
router("/users") { request ->
    Response(200, "用户")
}

// Get/set操作符用于类似映射的DSL
类 Configuration {
    private val values = mutableMapOf<String, Any>()

    operator fun get(key: String): Any? = values[key]
    operator fun set(key: String, value: Any) {
        values[key] = value
    }
}

val config1 = Configuration()
config1["timeout"] = 5000
val timeout1 = config1["timeout"]

中缀函数移除了二元操作的括号和点,而操作符重载使DSL中能够使用自然数学表示法。

使用@DslMarker的范围控制

DslMarker注解防止来自外部范围的隐式接收器,通过在编译时捕获意外嵌套错误来提高DSL安全性。

// 问题:没有@DslMarker的隐式接收器
类 Table(val name: String) {
    val columns = mutableListOf<Column>()

    fun column(name: String, init: Column.() -> Unit) {
        val column = Column(name)
        column.init()
        columns.add(column)
    }
}

类 Column(val name: String) {
    var type: String = "VARCHAR"
    var nullable: Boolean = true

    fun column(name: String, init: Column.() -> Unit) {
        // 意外从外部范围访问
    }
}

// 没有@DslMarker,这编译但错误
fun problematicDSL() = Table("users") {
    column("id") {
        type = "INT"
        // 这意外调用外部Table.column,而不是内部
        column("nested") {
            type = "TEXT"
        }
    }
}

// 解决方案:@DslMarker注解
@DslMarker
注解类 DatabaseDsl

@DatabaseDsl
类 SafeTable(val name: String) {
    val columns = mutableListOf<SafeColumn>()

    fun column(name: String, init: SafeColumn.() -> Unit) {
        val column = SafeColumn(name)
        column.init()
        columns.add(column)
    }
}

@DatabaseDsl
类 SafeColumn(val name: String) {
    var type: String = "VARCHAR"
    var nullable: Boolean = true
}

// 现在这不会编译 - @DslMarker防止隐式外部接收器
fun safeDSL() = SafeTable("users") {
    column("id") {
        type = "INT"
        // column("nested") { } // 编译错误!
    }
}

// 自定义DSL标记用于不同领域
@DslMarker
注解类 HtmlDsl

@DslMarker
注解类 TestDsl

@HtmlDsl
类 SafeHTML {
    fun body(init: SafeBody.() -> Unit) {
        SafeBody().init()
    }
}

@HtmlDsl
类 SafeBody {
    fun p(text: String) {}
}

@TestDsl
类 SafeTestSuite {
    fun test(name: String, block: SafeTestContext.() -> Unit) {
        SafeTestContext().block()
    }
}

@TestDsl
类 SafeTestContext {
    fun assertEquals(expected: Any, actual: Any) {}
}

// 多个DSL标记防止混合
fun mixedDSLs() {
    SafeHTML {
        body {
            // 不能在这里访问测试DSL
        }
    }

    SafeTestSuite {
        test("example") {
            // 不能在这里访问HTML DSL
        }
    }
}

DslMarker防止跨DSL边界的混淆隐式接收器访问,使DSL更安全、更可维护。

Gradle Kotlin DSL 模式

Gradle的Kotlin DSL展示了用于构建配置、依赖管理和任务定义的真实世界DSL模式。

// 构建脚本DSL模式
plugins {
    kotlin("jvm") version "1.9.0"
    id("application")
}

repositories {
    mavenCentral()
    google()
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
    testImplementation("org.junit.jupiter:junit-jupiter:5.9.0")
}

// 自定义任务与DSL
抽象类 CustomTask : DefaultTask() {
    @get:Input
    抽象 val message: Property<String>

    @TaskAction
    fun execute() {
        println(message.get())
    }
}

tasks {
    register<CustomTask>("greet") {
        message.set("Hello from custom task")
    }
}

// 扩展函数用于领域特定配置
fun Project.configureKotlin() {
    kotlin {
        jvmToolchain(17)
    }
}

fun Project.configureTesting() {
    tasks.withType<Test> {
        useJUnitPlatform()
    }
}

// 约定插件与DSL
抽象类 MyPluginExtension {
    抽象 val version: Property<String>
    抽象 val enabled: Property<Boolean>

    init {
        version.convention("1.0.0")
        enabled.convention(true)
    }
}

类 MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val extension = project.extensions.create(
            "myPlugin",
            MyPluginExtension::class.java
        )

        project.tasks.register("printConfig") {
            doLast {
                println("版本: ${extension.version.get()}")
                println("启用: ${extension.enabled.get()}")
            }
        }
    }
}

// 使用插件DSL
configure<MyPluginExtension> {
    version.set("2.0.0")
    enabled.set(true)
}

// 类型安全访问器
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
    jvmTarget = "17"
    freeCompilerArgs = listOf("-Xjsr305=strict")
}

// 多平台DSL
kotlin {
    jvm {
        withJava()
    }

    js(IR) {
        browser()
        nodejs()
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
            }
        }

        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }
    }
}

Gradle Kotlin DSL结合了多个DSL模式,以创建表达性强、类型安全的构建配置,具有出色的IDE支持。

Ktor DSL 用于Web应用

Ktor展示了用于路由、序列化和服务器配置的DSL模式在Web应用中。

// Ktor应用DSL
fun Application.module() {
    install(ContentNegotiation) {
        json(Json {
            prettyPrint = true
            isLenient = true
        })
    }

    routing {
        get("/") {
            call.respondText("Hello, world!")
        }

        get("/users/{id}") {
            val id = call.parameters["id"]
            call.respond(User(id?.toInt() ?: 0, "User $id"))
        }

        post("/users") {
            val user = call.receive<User>()
            call.respond(HttpStatusCode.Created, user)
        }

        route("/api") {
            get("/health") {
                call.respondText("OK")
            }

            authenticate("auth-jwt") {
                get("/protected") {
                    call.respondText("Protected route")
                }
            }
        }
    }
}

// 自定义Ktor DSL扩展
fun Route.userRoutes() {
    route("/users") {
        get {
            call.respond(listOf(User(1, "Alice"), User(2, "Bob")))
        }

        get("/{id}") {
            val id = call.parameters["id"]?.toInt() ?: return@get call.respond(
                HttpStatusCode.BadRequest
            )
            call.respond(User(id, "User $id"))
        }
    }
}

// 类型安全路由DSL
inline fun <reified T : Any> Route.typedGet(
    path: String,
    crossinline handler: suspend PipelineContext<Unit, ApplicationCall>.(T) -> Unit
) {
    get(path) {
        val params = call.receive<T>()
        handler(params)
    }
}

数据类 UserQuery(val name: String, val minAge: Int)

fun Route.typedRoutes() {
    typedGet<UserQuery>("/search") { query ->
        call.respondText("Searching for ${query.name}, age >= ${query.minAge}")
    }
}

Ktor的DSL提供了可读的、声明式的服务器配置,同时保持类型安全性和可组合性。

最佳实践

  1. 使用@DslMarker防止范围混淆 通过限制隐式接收器并在编译时捕获不正确的嵌套

  2. 保持DSL范围聚焦 在单个领域上,以保持清晰并防止在一个DSL中混合不相关概念

  3. 提供合理的默认值 在DSL构建器中,以减少样板代码同时允许需要时的自定义

  4. 利用带有接收器的lambda 用于上下文感知的DSL语法,无需限定符直接访问成员

  5. 谨慎使用中缀函数 仅用于自然的二元操作如比较、逻辑操作符或领域关系

  6. 用示例记录DSL使用 以展示预期模式并防止滥用灵活的DSL API

  7. 在构建时验证DSL结构 而不是运行时,以早期捕获错误并显示清晰的编译错误

  8. 尽可能使DSL不可变 以防止意外修改并启用更安全的并发使用

  9. 提供DSL和非DSL API 以给用户在表达性和显式性之间的选择

  10. 广泛测试DSL使用模式 以确保直观行为并捕获复杂嵌套场景中的边缘情况

常见陷阱

  1. 过度使用中缀函数 用于不适当的操作使代码更难阅读和理解,没有明确的约定

  2. 创建过于复杂的DSLs 尝试做太多导致混淆的API,难以学习和维护

  3. 忘记@DslMarker注解 允许来自外部范围的隐式接收器,在嵌套DSL结构中引起细微错误

  4. 不验证DSL结构 允许无效配置通过编译并在运行时失败

  5. 使用没有保护的可变DSL构建器 启用不安全的并发修改和意外行为

  6. 创建深度嵌套的DSLs 没有清晰的结构变得难以导航和推理

  7. 重载太多操作符 模糊意图并使代码神秘而不是表达性强

  8. 不一致地混合DSL和命令式代码 关于在何处使用哪种风格创建混淆

  9. 不提供清晰的DSL边界 使代码中DSL上下文开始和结束的位置不明确

  10. 忽略IDE支持影响 创建与自动完成或重构工具不兼容的DSLs

何时使用此技能

在构建库、框架或配置系统时使用Kotlin DSL模式,这些受益于可读的、类型安全的领域特定语法。

为层次结构如HTML生成、UI布局或嵌套配置树应用类型安全构建器,其中结构重要。

为具有自然二元操作的领域概念如比较、测量或逻辑关系使用中缀函数和操作符。

为测试框架、构建脚本或任何API利用带有接收器的lambda,其中上下文特定操作提高可读性。

在构建复杂嵌套DSLs时使用@DslMarker以防止范围混淆并在编译时而非运行时捕获错误。

资源