在Kotlin中,我想将扩展方法添加到类中,例如添加到类Entity
。但是我只想在Entity
在事务中时看到这些扩展,否则隐藏。例如,如果我定义这些类和扩展名:
interface Entity {}
fun Entity.save() {}
fun Entity.delete() {}
class Transaction {
fun start() {}
fun commit() {}
fun rollback() {}
}
我现在可能会在任何时候意外致电save()
和delete()
,但我只希望在事件的start()
之后,commit()
或{之后不再使用它们{1}}?目前我可以这样做,这是错误的:
rollback()
如何让它们在正确的背景下出现并消失?
注意: 此问题是由作者(Self-Answered Questions)故意编写和回答的,因此常见问题的Kotlin主题的惯用答案存在于SO中。还要澄清为Kotlin的alphas写的一些非常古老的答案,这些答案对于当前的Kotlin来说是不准确的。其他答案也很受欢迎,有很多样式可以解答这个问题!
答案 0 :(得分:19)
在Kotlin,我们倾向于使用传递给其他类的lambdas来给予他们"范围"或者执行在执行lambda之前和之后发生的行为,包括错误处理。因此,您首先需要更改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
的代码以提供范围。这是一个修改过的Transaction
类:
Transaction
这里有一个事务,在创建时,需要一个lambda来处理事务中的处理,如果没有抛出异常,它会自动提交事务。 (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() { ... }
}
类的构造函数的行为类似于Higher-Order Function)
我们还将Transaction
的扩展函数移到了Entity
之内,这样如果不在此类的上下文中,这些扩展函数将不会被看到也无法调用。这包括Transaction
和commit()
的方法,这些方法现在只能在类本身内调用,因为它们现在是类中的扩展函数。
由于收到的lambda是rollback()
的扩展函数,因此它在该类的上下文中运行,因此可以看到扩展。 (见:Function Literals with Receiver)
这个旧代码现在无效,编译器给我们一个错误:
Transaction
现在你要写代码而不是存在于fun changePerson(person: Person) {
person.name = "Fred"
person.save() // ERROR: unresolved reference: save()
}
块中:
Transaction
传入的lambda被推断为fun actsInMovie(actor: Person, film: Movie) {
Transaction { // optional parenthesis omitted
if (actor.winsAwards()) {
film.addActor(actor)
save(film)
} else {
rollback()
}
}
}
上的扩展函数,因为它没有正式声明。
连接一堆这些"行动"在事务中,只需创建一系列可在事务中使用的扩展函数,例如:
Transaction
创建更多这样的,然后在传递给Transaction ...的lambda中使用它们
fun Transaction.actsInMovie(actor: Person, film: Movie) {
film.addActor(actor)
save(film)
}
现在回到最初的问题,我们有交易方法和实体方法只出现在正确的时刻。因为使用lambdas或匿名函数的副作用是我们最终会探索关于如何编写代码的新想法。
答案 1 :(得分:11)
请参阅other answer了解主要主题和基础知识,此处为更深层次的水域......
我们没有解决您可能遇到的所有问题。很容易使一些扩展函数出现在另一个类的上下文中。但是同时让两件事情变得如此容易,这并不容易。例如,如果我希望Movie
方法addActor()
仅在Transaction
块内显示,则更难。 addActor()
方法不能同时拥有两个接收器。所以我们要么有一个接收两个参数Transaction.addActorToMovie(actor, movie)
的方法,要么我们需要另一个计划。
这样做的一种方法是使用中间对象来扩展系统。现在,以下示例可能是也可能不合理,但它显示了如何仅根据需要进行此额外级别的公开功能。以下是代码,我们更改Transaction
以实现接口Transactable
,以便我们现在可以随时delegate to the interface。
当我们添加新功能时,我们可以创建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 { ... }
稍后,我们决定添加:
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)
}
现在使用新功能:
fun castChanges(swaps: Pair<Person, Person>, film: Movie) {
Transaction {
forMovie(film) {
swaps.forEach {
// only available here inside forMovie() lambda
swapActor(it.first, it.second)
}
}
}
}
或者这整件事可能只是Transactable
上的顶级扩展函数,如果你不介意它处于顶级,而不是在类中,并且使包的命名空间变得混乱。
有关使用中间类的其他示例,请参阅:
config.value("something").asString()
(code link)connect(node).edge(relation).to(otherNode)
的另一部分。 (code link)同一模块中的test cases会显示更多用途,包括get()
和invoke()
等运算符仅在上下文中可用的情况。