在Kotlin中,如何将扩展方法添加到另一个类中,但仅在特定的上下文中才可见?
在Kotlin中,我想将扩展方法添加到类中,例如添加到Entity
类中。 但是我只想在Entity
处于事务中时看到这些扩展,否则就隐藏了。 例如,如果我定义这些类和扩展:
interface Entity {} fun Entity.save() {} fun Entity.delete() {} class Transaction { fun start() {} fun commit() {} fun rollback() {} }
我现在可以在任何时候不小心调用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
我如何让它们在正确的环境中出现和消失?
注意: 这个问题是由作者故意写的和回答的( 自我回答的问题 ),所以对于常见的Kotlin话题的习惯性的回答是在SO中。 此外,为了澄清一些真正的古老的答案写为科特林的阿尔法,是不是今天的Kotlin准确。 其他答案也欢迎,有很多款式如何回答这个问题!
基础:
在Kotlin中,我们倾向于使用传递给其他类的lambda表达式来给它们“范围”,或者在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() { ... } }
这里我们有一个事务,当创建时,需要一个lambda来处理事务中的内部处理,如果没有异常被抛出,它会自动提交事务。 ( Transaction
类的构造函数像高阶函数一样工作 )
我们也把Entity
的扩展函数移到了Transaction
这样这些扩展函数就不会被看到,也不会被调用。 这包括commit()
和rollback()
的方法,它们只能从类本身中立即调用,因为它们现在是范围在类中的扩展函数。
由于接收到的lambda是Transaction
的扩展函数,因此它在该类的上下文中运行,因此可以看到扩展。 (请参阅: 带接收器的函数文字 )
这个旧的代码现在是无效的,编译器给我们一个错误:
fun changePerson(person: Person) { person.name = "Fred" person.save() // ERROR: unresolved reference: save() }
现在,您将编写代码来存在于Transaction
块中:
fun actsInMovie(actor: Person, film: Movie) { Transaction { // optional parenthesis omitted if (actor.winsAwards()) { film.addActor(actor) save(film) } else { rollback() } } }
因为它没有正式声明,所以被传入的lambda被推断为Transaction
上的扩展函数。
要在事务中将大量这些“操作”链接在一起,只需创建一系列可在事务中使用的扩展函数,例如:
fun Transaction.actsInMovie(actor: Person, film: Movie) { film.addActor(actor) save(film) }
创建更多像这样,然后在传递给交易的lambda中使用它们…
Transaction { actsInMovie(harrison, starWars) actsInMovie(carrie, starWars) directsMovie(abrams, starWars) rateMovie(starWars, 5) }
现在回到原来的问题,我们有事务方法和实体方法只出现在正确的时间。 而作为使用lambdas或匿名函数的一个副作用,我们最终将探索关于如何编写代码的新想法。
看到主要的话题和基础的其他答案 , 在这里是更深的水域…
相关的高级主题:
我们没有解决你可能遇到的任何问题。 在另一个类的上下文中出现一些扩展函数是很容易的。 但同时做两件事情并不容易。 例如,如果我希望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 { ... }
稍后,我们决定添加:
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
的顶级扩展函数。
有关使用中间类的其他示例,请参阅:
- 在Klutter TypeSafe配置模块中,中间对象用来存储“哪个属性”可以被执行的状态,所以它可以被传递并且改变其他可用的方法。
config.value("something").asString()
( code link ) - 在Klutter Netflix Graph模块中,中间对象用于转换到DSL语法
connect(node).edge(relation).to(otherNode)
另一部分。 ( 代码链接 )同一模块中的测试用例显示了更多的用途,包括get()
和invoke()
等运算符在上下文中是否可用。