我试图找到解决范围问题的完美解决方案,我真的很想听听您的意见。
我有一些无法更改的第三方类:
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
}
}
不幸的是,现在我遇到了臭名昭著的错误:
error: 'inline fun Employee.addCard(init: (Scratch_1.Card).() -> Unit): Unit' can't be called in this context by implicit receiver. Use the explicit one if necessary
我了解错误的原因,并且想考虑解决方案。
删除 DSLMarker 注释以便能够继承父范围。不幸的是,这允许非法构建者使用:
with(Builder) { val built = employee { id = 5 name = "max" addCard { employee { // ... } cardId = 5 } } }
使用限定的 this 来访问父作用域。但随后我们必须使用另一个合格的接收器来获得合适的接收器。这相当冗长。
with(Builder) { val built = employee { id = 5 name = "max" with(this@with) { <a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d8acb0b1ab98bdb5a8b4b7a1bdbdf6b9bcbc9bb9aabc" rel="noreferrer noopener nofollow">[email protected]</a> { cardId = 5 } } } }
继承employee,以便能够将扩展函数放入其中(这里不可能进行委托(delegate),因为我在Employee中有很多属性,并且并非所有属性都由接口(interface)定义)。 如果第三方类是最终的,这并不总是有效。
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 } }
那么最好的解决方案是什么?我错过了什么吗? 非常感谢您阅读所有这些!
最佳答案
- 您可以定义extension function不产生新类(Class), 它也适用于外国不可触及的来源:
@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 example
关于kotlin - 控制 Kotlin DSL 构建器中的范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55684541/