ZetCode

Kotlin setparam 关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的 setparam 关键字是一个强大的工具,用于创建 DSL 和 Builder 模式。它允许以清晰、可读的方式修改参数。本教程通过实际示例深入探讨了 setparam

基本定义

Kotlin 中的 setparam 关键字用于标记可以在 DSL 上下文中修改的参数。它通常与属性委托或 Builder 模式一起使用,以实现流畅的 API 创建。该关键字有助于创建更具表现力和可维护性的代码。

setparam 的基本用法

setparam 最简单的用法是标记一个参数,使其在 DSL 上下文中可修改。这使得在您的 DSL 中可以使用简洁的属性设置语法。

BasicSetparam.kt
package com.zetcode

class Person {
    var name: String by setparam("Unknown")
    var age: Int by setparam(0)
}

fun person(block: Person.() -> Unit): Person {
    val p = Person()
    p.block()
    return p
}

fun main() {
    val p = person {
        name = "John Doe"
        age = 30
    }
    
    println("${p.name}, ${p.age}") // Output: John Doe, 30
}

在这里,我们使用 setparam 创建一个具有属性的 Person 类。person 函数提供类似 DSL 的语法来设置属性。这些属性可以直接在块中设置,语法清晰。

使用 setparam 的自定义属性委托

setparam 与自定义属性委托一起使用,以便在属性设置期间提供额外的功能。这使得验证或副作用成为可能。

CustomDelegate.kt
package com.zetcode

class ValidatedString(private var value: String) {
    operator fun getValue(thisRef: Any?, property: Any?) = value
    
    operator fun setValue(thisRef: Any?, property: Any?, newValue: String) {
        require(newValue.isNotBlank()) { "Name cannot be blank" }
        value = newValue
    }
}

class User {
    var name: String by setparam(ValidatedString(""))
}

fun user(block: User.() -> Unit): User {
    val u = User()
    u.block()
    return u
}

fun main() {
    val u = user {
        name = "Alice"
    }
    
    println(u.name) // Output: Alice
    
    // This would throw IllegalArgumentException:
    // user { name = "" }
}

此示例显示了带有验证的自定义属性委托。setparam 关键字启用简洁的设置语法,而委托确保名称不为空白。当设置属性时,会发生验证。

使用 setparam 的 Builder 模式

setparam 在 Builder 模式中特别有用,允许流畅的属性设置,同时保持构建对象的不可变性。

BuilderPattern.kt
package com.zetcode

data class Car(
    val make: String,
    val model: String,
    val year: Int
) {
    class Builder {
        var make: String by setparam("")
        var model: String by setparam("")
        var year: Int by setparam(0)
        
        fun build() = Car(make, model, year)
    }
}

fun car(block: Car.Builder.() -> Unit): Car {
    val builder = Car.Builder()
    builder.block()
    return builder.build()
}

fun main() {
    val myCar = car {
        make = "Toyota"
        model = "Corolla"
        year = 2022
    }
    
    println(myCar) // Output: Car(make=Toyota, model=Corolla, year=2022)
}

在这里,我们使用 setparam 实现 Builder 模式。属性在类似 DSL 的块中设置,最终对象是不可变的。这结合了 DSL 的可读性和不可变性的安全性。

使用 setparam 创建 DSL

setparam 在创建特定于领域的语言 (DSL) 时表现出色。它允许使用类似自然语言的语法来配置对象。

DslCreation.kt
package com.zetcode

class Configuration {
    var host: String by setparam("localhost")
    var port: Int by setparam(8080)
    var timeout: Long by setparam(5000L)
}

fun config(block: Configuration.() -> Unit): Configuration {
    val config = Configuration()
    config.block()
    return config
}

fun main() {
    val cfg = config {
        host = "example.com"
        port = 9000
        timeout = 10000L
    }
    
    println("""
        Host: ${cfg.host}
        Port: ${cfg.port}
        Timeout: ${cfg.timeout}
    """.trimIndent())
}

此示例演示了使用 setparam 的配置 DSL。生成的语法清晰直观。每个属性都可以在配置块中直接设置,使代码具有自文档性。

使用 setparam 的嵌套 DSL

setparam 可用于创建嵌套 DSL 结构,允许复杂的层次结构配置,同时保持可读性。

NestedDsl.kt
package com.zetcode

class Address {
    var street: String by setparam("")
    var city: String by setparam("")
    var zip: String by setparam("")
}

class Person {
    var name: String by setparam("")
    var address: Address by setparam(Address())
}

fun person(block: Person.() -> Unit): Person {
    val p = Person()
    p.block()
    return p
}

fun Person.address(block: Address.() -> Unit) {
    address = Address().apply(block)
}

fun main() {
    val p = person {
        name = "Bob Smith"
        address {
            street = "123 Main St"
            city = "Springfield"
            zip = "12345"
        }
    }
    
    println("""
        ${p.name}
        ${p.address.street}
        ${p.address.city}, ${p.address.zip}
    """.trimIndent())
}

此示例显示了使用 setparam 的嵌套 DSL 结构。address 块允许以清晰、层次化的方式设置地址属性。生成的代码具有高度的可读性和可维护性。

使用 setparam 的类型安全 Builder

setparam 与 Kotlin 的类型安全 Builder 模式相结合,可以创建既具有表现力又类型安全的强大 DSL。

TypeSafeBuilder.kt
package com.zetcode

class Html {
    private val children = mutableListOf()
    
    fun p(block: P.() -> Unit) {
        children.add(P().apply(block))
    }
    
    override fun toString() = children.joinToString("\n")
}

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

fun html(block: Html.() -> Unit): Html {
    return Html().apply(block)
}

fun main() {
    val page = html {
        p {
            text = "Hello, world!"
        }
        p {
            text = "This is HTML DSL"
        }
    }
    
    println(page)
}

此示例使用 setparam 创建一个类型安全的 HTML Builder。p 函数创建带有文本内容的段落元素。setparam 能够在 Builder 块内进行清晰的属性设置。

带有自定义逻辑的 Advanced setparam

setparam 可以与自定义逻辑结合使用,以创建复杂的 DSL,其中包括验证、转换或其他自定义行为。

AdvancedSetparam.kt
package com.zetcode

class Task {
    private var _name: String = ""
    var name: String by setparam("")
        set(value) {
            require(value.length <= 50) { "Name too long" }
            _name = value
        }
    
    var priority: Int by setparam(0)
        set(value) {
            require(value in 1..10) { "Priority must be 1-10" }
            field = value
        }
}

fun task(block: Task.() -> Unit): Task {
    return Task().apply(block)
}

fun main() {
    val importantTask = task {
        name = "Implement feature X"
        priority = 8
    }
    
    println("${importantTask.name} (Priority: ${importantTask.priority})")
    
    // These would throw exceptions:
    // task { name = "A".repeat(51) }
    // task { priority = 11 }
}

此高级示例显示了带有自定义验证逻辑的 setparam。属性包括在设置值时检查的要求。DSL 语法保持清晰,同时强制执行业务规则。

setparam 的最佳实践

来源

Kotlin 类型安全 Builder 文档

本教程深入探讨了 Kotlin 的 setparam 关键字,展示了它在 DSL 创建、Builder 模式和属性委托中的应用。我们探讨了从基本用法到高级模式的各种场景。正确使用 setparam 可以显著提高代码的可读性和可维护性。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有多年的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我撰写了 1400 多篇文章和 8 本电子书。 我有超过八年的编程教学经验。

列出 所有 Kotlin 教程