Jay*_*ard 21 dsl builder kotlin
在Kotlin中,我想将扩展方法添加到类中,例如添加到类中Entity.但我只想Entity在交易中看到这些扩展,否则隐藏.例如,如果我定义这些类和扩展:
interface Entity {}
fun Entity.save() {}
fun Entity.delete() {}
class Transaction {
fun start() {}
fun commit() {}
fun rollback() {}
}
Run Code Online (Sandbox Code Playgroud)
我现在可以叫意外save(),并delete()在任何时候,但我只希望他们可以在后start()一交易后不再commit()或rollback()?目前我可以这样做,这是错误的:
someEntity.save() // DO NOT WANT TO ALLOW HERE
val tx = Transaction()
tx.start()
someEntity.save() // YES, ALLOW
tx.commit()
someEntity.delete() // DO NOT WANT TO ALLOW HERE
Run Code Online (Sandbox Code Playgroud)
如何使它们在正确的上下文中显示和消失?
注意: 这个问题是由作者故意编写和回答的(自答案问题),因此对于常见问题的Kotlin主题的惯用答案存在于SO中.还要澄清为Kotlin的alphas写的一些非常古老的答案,这些答案对于当前的Kotlin来说是不准确的.其他答案也欢迎,有很多样式如何回答这个问题!
Jay*_*ard 20
在Kotlin中,我们倾向于使用传递给其他类的lambdas来赋予它们"范围"或者在执行lambda之前和之后发生的行为,包括错误处理.因此,您首先需要更改代码Transaction以提供范围.这是一个修改过的Transaction类:
class Transaction(withinTx: Transaction.() -> Unit) {
init {
start()
try {
// now call the user code, scoped to this transaction class
this.withinTx()
commit()
}
catch (ex: Throwable) {
rollback()
throw ex
}
}
private fun Transaction.start() { ... }
fun Entity.save(tx: Transaction) { ... }
fun Entity.delete(tx: Transaction) { ... }
fun Transaction.save(entity: Entity) { entity.save(this) }
fun Transaction.delete(entity: Entity) { entity.delete(this) }
fun Transaction.commit() { ... }
fun Transaction.rollback() { ... }
}
Run Code Online (Sandbox Code Playgroud)
这里我们有一个事务,在创建时需要一个lambda来处理事务中的处理,如果没有抛出异常,它会自动提交事务.(Transaction类的构造函数就像一个高阶函数)
我们还将扩展函数移到了Entity内部,Transaction这样如果不在此类的上下文中,这些扩展函数将不会被看到也无法调用.这包括方法commit()和rollback()只能从类内部现在所谓的,因为他们现在是类中范围的扩展功能.
由于接收到的lambda是一个扩展函数,Transaction因此它在该类的上下文中运行,因此可以看到扩展.(参见:带接收器的函数文字)
这个旧代码现在无效,编译器给我们一个错误:
fun changePerson(person: Person) {
person.name = "Fred"
person.save() // ERROR: unresolved reference: save()
}
Run Code Online (Sandbox Code Playgroud)
现在你要写代码而不是存在于一个Transaction块中:
fun actsInMovie(actor: Person, film: Movie) {
Transaction { // optional parenthesis omitted
if (actor.winsAwards()) {
film.addActor(actor)
save(film)
} else {
rollback()
}
}
}
Run Code Online (Sandbox Code Playgroud)
传入的lambda被推断为扩展函数,Transaction因为它没有正式声明.
要在事务中将一堆这些"动作"链接在一起,只需创建一系列可在事务中使用的扩展函数,例如:
fun Transaction.actsInMovie(actor: Person, film: Movie) {
film.addActor(actor)
save(film)
}
Run Code Online (Sandbox Code Playgroud)
创建更多这样的,然后在传递给Transaction的lambda中使用它们...
Transaction {
actsInMovie(harrison, starWars)
actsInMovie(carrie, starWars)
directsMovie(abrams, starWars)
rateMovie(starWars, 5)
}
Run Code Online (Sandbox Code Playgroud)
现在回到最初的问题,我们有交易方法和实体方法只出现在正确的时刻.使用lambdas或匿名函数的副作用是我们最终探索关于如何编写代码的新想法.
Jay*_*ard 12
请参阅主题和基础知识的其他答案,这里是更深层次的...
我们没有解决您可能遇到的所有问题.很容易使一些扩展函数出现在另一个类的上下文中.但要同时为两件事做这件事并不容易.例如,如果我希望Movie方法addActor()仅在Transaction块内出现,则更难.该addActor()方法不能同时具有两个接收器.所以我们要么有一个接收两个参数的方法,Transaction.addActorToMovie(actor, movie)要么我们需要另一个计划.
一种方法是使用中间对象来扩展系统.现在,以下示例可能是也可能不合理,但它显示了如何仅根据需要进行此额外级别的公开功能.这是代码,我们在其中更改Transaction以实现接口,Transactable以便我们现在可以随时委托给接口.
当我们添加新功能时,我们可以创建Transactable公开这些功能的新实现,并保持临时状态.然后,一个简单的辅助函数可以轻松访问这些隐藏的新类.所有添加都可以在不修改核心原始类的情况下完成.
核心课程:
interface Entity {}
interface Transactable {
fun Entity.save(tx: Transactable)
fun Entity.delete(tx: Transactable)
fun Transactable.commit()
fun Transactable.rollback()
fun Transactable.save(entity: Entity) { entity.save(this) }
fun Transactable.delete(entity: Entity) { entity.save(this) }
}
class Transaction(withinTx: Transactable.() -> Unit) : Transactable {
init {
start()
try {
withinTx()
commit()
} catch (ex: Throwable) {
rollback()
throw ex
}
}
private fun start() { ... }
override fun Entity.save(tx: Transactable) { ... }
override fun Entity.delete(tx: Transactable) { ... }
override fun Transactable.commit() { ... }
override fun Transactable.rollback() { ... }
}
class Person : Entity { ... }
class Movie : Entity { ... }
Run Code Online (Sandbox Code Playgroud)
后来,我们决定添加:
class MovieTransactions(val movie: Movie,
tx: Transactable,
withTx: MovieTransactions.()->Unit): Transactable by tx {
init {
this.withTx()
}
fun swapActor(originalActor: Person, replacementActor: Person) {
// `this` is the transaction
// `movie` is the movie
movie.removeActor(originalActor)
movie.addActor(replacementActor)
save(movie)
}
// ...and other complex functions
}
fun Transactable.forMovie(movie: Movie, withTx: MovieTransactions.()->Unit) {
MovieTransactions(movie, this, withTx)
}
Run Code Online (Sandbox Code Playgroud)
现在使用新功能:
fun castChanges(swaps: Pair<Person, Person>, film: Movie) {
Transaction {
forMovie(film) {
swaps.forEach {
// only available here inside forMovie() lambda
swapActor(it.first, it.second)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
或者,Transactable如果您不介意它处于顶层,而不是在类中,并且使包的命名空间变得混乱,那么整个事情可能只是一个顶级扩展函数.
有关使用中间类的其他示例,请参阅:
config.value("something").asString()(代码链接)connect(node).edge(relation).to(otherNode).(代码的链接)的测试用例在同一模块中表现出更多的用途,包括如何甚至运营商,如get()和invoke()仅在上下文中是可用的.