kotlin - 在 Kotlin DSL 构建器中控制范围
问题描述
我试图为我的范围问题找到完美的解决方案,我真的很想听听你的意见。
我有一些无法更改的第三方课程:
class Employee {
var id = 0
var name = ""
var card : Card? = null
// ...
}
class Card {
var cardId = 0
}
我的目标是能够建立这样的员工:
val built = employee {
id = 5
name = "max"
addCard {
cardId = 5
}
}
原始 bean中没有方法addCard。因此,我想出了以下构建器:
@DslMarker
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class Scoped
@Scoped
object Builder {
inline fun employee (init: (@Scoped Employee).() -> Unit): Employee {
val e = Employee()
e.init()
return e
}
inline fun Employee.addCard(init: (@Scoped Card).() -> Unit) {
val c = Card()
c.init()
card = c
}
}
不幸的是,现在我得到了臭名昭著的错误:
错误:'inline fun Employee.addCard(init: (Scratch_1.Card).() -> Unit): Unit' 不能在此上下文中被隐式接收器调用。必要时使用显式的
我了解错误的原因,我想考虑解决方案。
删除 DSLMarker 注释以能够继承父范围。不幸的是,这允许非法使用构建器:
with(Builder) { val built = employee { id = 5 name = "max" addCard { employee { // ... } cardId = 5 } } }
使用限定的 this 来访问父作用域。但是我们必须使用另一个合格的 this 来获得正确的接收器。这很冗长。
with(Builder) { val built = employee { id = 5 name = "max" with(this@with) { this@employee.addCard { cardId = 5 } } } }
继承员工可以把扩展功能放在里面(委托在这里是不可能的,因为我在Employee中有很多属性,并且没有全部由接口定义)。如果第三方类是最终的,这并不总是有效。
class EmployeeEx : Employee() { inline fun addCard(init: (@Scoped Card).() -> Unit) { val c = Card() c.init() card = c } }
和建设者:
@Scoped object Builder { inline fun employee (init: (@Scoped EmployeeEx).() -> Unit): Employee { val e = EmployeeEx() e.init() return e } }
那么最好的解决方案是什么?我错过了什么吗?非常感谢您阅读所有这些!
解决方案
- 您可以在不产生新类的情况下定义扩展功能,它也适用于外国不可接触的来源:
@DslMarker
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class Scoped
@Scoped
object Builder {
inline fun employee(init: (@Scoped Employee).() -> Unit) = Employee().apply(init)
}
fun Employee.addCard(init: (@Scoped Card).() -> Unit) = run { card = Card().apply(init) }
- 控制 dsl 范围的经典工具有两种:
@DSLMarker
对于您正在使用的可实现的代码,以及@Deprecated (level = ERROR)
对于第一种方法不起作用的所有其他情况。
例如,目前您可以构建嵌入式员工:
val built = employee {
id = 5
name = "max"
addCard {
cardId = 6
}
employee { } // <--- compilable, but does not have sense
}
您可以通过弃用的方式直接禁止此操作:
@Deprecated(level = DeprecationLevel.ERROR, message = "No subcontractors, please.")
fun Employee.employee(init: (@Scoped Employee).() -> Unit) = Unit
现在以下示例不可编译:
val built = employee {
//...
employee { } // <--- compilation error with your message
}
- 您可能会发现这很有用:Kotlin DSL 示例
推荐阅读
- typescript - 如何通过包装角度材料6工具栏组件创建自定义组件时一个接一个地投影mat-toolbar-row内容
- gcc - 内联汇编的 gcc 编译错误:ljmp 的操作数类型不匹配
- excel - 如何从 Excel 工作表写入已创建的 Word 文档中的表格
- reactjs - React Navigation 将自定义数据传递到堆叠的屏幕
- hadoop - 使用 API 在 Hadoop 中移动文件
- javascript - 相互作用。页面加载时的 Blast.js 动画
- apache-spark-sql - 在 hdfs 中写入小文件或使用 coalesce
- amazon-web-services - AWS Glue,将 CSV 传输到 Redshift
- sql - 在 MS Access VBA 中使用 Like 运算符和通配符
- php - Laravel 在队列上创建作业