Kotlin中无需重写所有属性的建造者模式替代方案有哪些?
Kotlin 无需重写属性的封装式对象构建/更新方案
你的代码核心问题是:private set修饰的属性无法在类外部的lambda中直接赋值,因为lambda的执行作用域不属于UserEntity内部。以下是几种无需手动重写所有属性的替代方案:
方案1:利用Kotlin反射实现通用配置
通过反射可以在配置lambda中修改private属性,无需为每个属性编写单独的set方法。需要先添加kotlin-reflect依赖到项目中。
修改后的代码:
import java.time.OffsetDateTime import java.time.OffsetDateTime.now import java.util.UUID import java.util.UUID.randomUUID import kotlin.reflect.KMutableProperty import kotlin.reflect.full.memberProperties data class UserEntity( val id: UUID = randomUUID(), ) { var firstName: String = "" private set var lastName: String = "" private set var mainEmail: String = "" private set var isActive: Boolean = true private set var createdAt: OffsetDateTime = now() private set companion object { fun new(id: UUID = randomUUID(), block: UserEntity.() -> Unit): UserEntity { return UserEntity(id).apply { val user = this block(object : UserEntity() { override fun setPropertyValue(name: String, value: Any?) { user::class.memberProperties .filterIsInstance<KMutableProperty<*>>() .firstOrNull { it.name == name } ?.let { prop -> prop.isAccessible = true @Suppress("UNCHECKED_CAST") (prop as KMutableProperty<Any?>).set(user, value) } ?: error("Property $name not found in UserEntity") } }) }.also { it.validate() } } } fun update(block: UserEntity.() -> Unit) { val user = this block(object : UserEntity() { override fun setPropertyValue(name: String, value: Any?) { user::class.memberProperties .filterIsInstance<KMutableProperty<*>>() .firstOrNull { it.name == name } ?.let { prop -> prop.isAccessible = true @Suppress("UNCHECKED_CAST") (prop as KMutableProperty<Any?>).set(user, value) } ?: error("Property $name not found in UserEntity") } }) validate() } protected open fun setPropertyValue(name: String, value: Any?) {} private fun validate(){ require(firstName.isNotBlank()) { "First name cannot be blank" } require(mainEmail.isNotBlank()) { "Main email cannot be blank" } } } // 使用方式不变 val userEntity = UserEntity.new { firstName = "User" lastName = "Last" mainEmail = "user.last@email.com" } userEntity.update { firstName = "Qwerty" lastName = "Another" mainEmail = "qwerty.another@email.com" }
该方案通过继承UserEntity的匿名代理类,结合反射机制实现属性修改,无需手动编写每个属性的set方法,保持配置lambda的简洁性。
方案2:使用internal setter限制修改范围
如果你的项目是单模块结构,可将属性setter改为internal,这样模块内部可以自由修改属性,外部模块无法直接赋值,同时保留简洁的lambda配置语法:
import java.time.OffsetDateTime import java.time.OffsetDateTime.now import java.util.UUID import java.util.UUID.randomUUID data class UserEntity( val id: UUID = randomUUID(), ) { var firstName: String = "" internal set var lastName: String = "" internal set var mainEmail: String = "" internal set var isActive: Boolean = true internal set var createdAt: OffsetDateTime = now() internal set companion object { fun new(id: UUID = randomUUID(), block: UserEntity.() -> Unit): UserEntity { return UserEntity(id).apply(block).also { it.validate() } } } fun update(block: UserEntity.() -> Unit) { this.apply(block).also { it.validate() } } private fun validate(){ require(firstName.isNotBlank()) { "First name cannot be blank" } require(mainEmail.isNotBlank()) { "Main email cannot be blank" } } } // 使用方式完全不变 val userEntity = UserEntity.new { firstName = "User" lastName = "Last" mainEmail = "user.last@email.com" } userEntity.update { firstName = "Qwerty" lastName = "Another" mainEmail = "qwerty.another@email.com" }
这是最简单的方案,利用Kotlin的internal可见性修饰符实现模块内封装,适合单模块项目,无需额外代码。
方案3:DSL作用域类(无反射)
如果不想依赖反射,也可以创建一个内部DSL作用域类,通过重载赋值操作符来映射属性修改,虽然需要列举一次属性,但比手动写Builder类更简洁:
import java.time.OffsetDateTime import java.time.OffsetDateTime.now import java.util.UUID import java.util.UUID.randomUUID import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty data class UserEntity( val id: UUID = randomUUID(), ) { var firstName: String = "" private set var lastName: String = "" private set var mainEmail: String = "" private set var isActive: Boolean = true private set var createdAt: OffsetDateTime = now() private set inner class ConfigScope { operator fun String.setValue(thisRef: Any?, property: KProperty<*>, value: String) { when (property.name) { "firstName" -> this@UserEntity.firstName = value "lastName" -> this@UserEntity.lastName = value "mainEmail" -> this@UserEntity.mainEmail = value } } operator fun Boolean.setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { if (property.name == "isActive") { this@UserEntity.isActive = value } } operator fun OffsetDateTime.setValue(thisRef: Any?, property: KProperty<*>, value: OffsetDateTime) { if (property.name == "createdAt") { this@UserEntity.createdAt = value } } } companion object { fun new(id: UUID = randomUUID(), block: ConfigScope.() -> Unit): UserEntity { val user = UserEntity(id) user.ConfigScope().apply(block) user.validate() return user } } fun update(block: ConfigScope.() -> Unit) { ConfigScope().apply(block) validate() } private fun validate(){ require(firstName.isNotBlank()) { "First name cannot be blank" } require(mainEmail.isNotBlank()) { "Main email cannot be blank" } } } // 使用方式不变 val userEntity = UserEntity.new { firstName = "User" lastName = "Last" mainEmail = "user.last@email.com" } userEntity.update { firstName = "Qwerty" lastName = "Another" mainEmail = "qwerty.another@email.com" }
该方案通过内部类ConfigScope重载不同类型的赋值操作符,将lambda中的赋值映射到原对象的private属性,无需重写所有属性,且不依赖反射。
内容的提问来源于stack exchange,提问作者Javarian
相关产品推荐
相关产品推荐

