在 Kotlin 中,如何将扩展方法添加到另一个 class,但仅在特定上下文中可见?
In Kotlin, how do I add extension methods to another class, but only visible in a certain context?
在 Kotlin 中,我想将扩展方法添加到 class,例如添加到 class Entity
。但我只想在 Entity
在交易中时看到这些扩展,否则隐藏。例如,如果我定义这些 classes 和扩展:
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
如何让它们在正确的上下文中出现和消失?
注意: 这个问题是作者(Self-Answered Questions)有意写下并回答的,以便对常见的 Kotlin 主题进行地道的回答存在于 SO 中。还要澄清一些为 Kotlin alpha 编写的非常古老的答案,这些答案对于当今的 Kotlin 来说并不准确。也欢迎其他回答,这个回答的方式有很多种!
基础知识:
在 Kotlin 中,我们倾向于使用传递给其他 classes 的 lambda 来为它们提供 "scope" 或在执行 lambda 之前和之后发生的行为,包括错误处理。因此,您首先需要更改 Transaction
的代码以提供范围。这是修改后的 Transaction
class:
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
class 的构造函数就像 Higher-Order Function)
我们还将 Entity
的扩展函数移到了 Transaction
中,这样如果不在此 class 的上下文中,就不会看到或调用这些扩展函数。这包括 commit()
和 rollback()
的方法,它们现在只能从 class 本身内部调用,因为它们现在是 class 范围内的扩展函数。
由于接收到的 lambda 是 Transaction
的扩展函数,它在 class 的上下文中运行,因此可以看到扩展。 (参见:Function Literals with Receiver)
这段旧代码现在无效,编译器给我们一个错误:
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
上的扩展函数,因为它没有正式声明。
要在交易中将这些 "actions" 链接在一起,只需创建一系列可在交易中使用的扩展函数,例如:
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)
}
现在回到最初的问题,我们的交易方法和实体方法只在正确的时间出现。作为使用 lambda 或匿名函数的副作用,我们最终会探索有关代码组成方式的新想法。
请参阅 了解主要主题和基础知识,这里是更深的水域...
相关高级主题:
我们不会解决您可能 运行 到这里的所有问题。很容易让一些扩展函数出现在另一个 class 的上下文中。但是同时为两件事情做这项工作并不是那么容易。例如,如果我想让 Movie
方法 addActor()
只出现在 Transaction
块内,那就更难了。 addActor()
方法不能同时有两个接收者。所以我们要么有一个接收两个参数 Transaction.addActorToMovie(actor, movie)
的方法,要么我们需要另一个计划。
实现此目的的一种方法是使用我们可以扩展系统的中间对象。现在,下面的示例可能合理也可能不合理,但它显示了如何仅根据需要进行这种额外级别的公开功能。这是代码,我们在其中更改 Transaction
以实现接口 Transactable
以便我们现在可以随时 delegate to the interface 。
当我们添加新功能时,我们可以创建 Transactable
的新实现来公开这些函数并保存临时状态。然后一个简单的辅助函数可以很容易地访问这些隐藏的新 classes。所有添加都可以在不修改核心原classes.
的情况下完成
核心classes:
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)
}
}
}
}
或者,如果您不介意它位于顶层而不是 class 中,那么整个事情可能只是 Transactable
上的顶层扩展函数包的命名空间。
有关使用中介 classes 的其他示例,请参阅:
- 在 Klutter TypeSafe 配置模块中,一个中间对象用于存储 "which property" 的状态,可以对其进行操作,因此它可以传递并改变其他可用的方法。
config.value("something").asString()
(code link)
- 在 Klutter Netflix Graph 模块中,一个中间对象用于转换到 DSL 语法的另一部分
connect(node).edge(relation).to(otherNode)
。 (同一模块中的 code link) The test cases 显示更多用途,包括 get()
和 invoke()
等运算符如何仅在上下文中可用。
在 Kotlin 中,我想将扩展方法添加到 class,例如添加到 class Entity
。但我只想在 Entity
在交易中时看到这些扩展,否则隐藏。例如,如果我定义这些 classes 和扩展:
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
如何让它们在正确的上下文中出现和消失?
注意: 这个问题是作者(Self-Answered Questions)有意写下并回答的,以便对常见的 Kotlin 主题进行地道的回答存在于 SO 中。还要澄清一些为 Kotlin alpha 编写的非常古老的答案,这些答案对于当今的 Kotlin 来说并不准确。也欢迎其他回答,这个回答的方式有很多种!
基础知识:
在 Kotlin 中,我们倾向于使用传递给其他 classes 的 lambda 来为它们提供 "scope" 或在执行 lambda 之前和之后发生的行为,包括错误处理。因此,您首先需要更改 Transaction
的代码以提供范围。这是修改后的 Transaction
class:
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
class 的构造函数就像 Higher-Order Function)
我们还将 Entity
的扩展函数移到了 Transaction
中,这样如果不在此 class 的上下文中,就不会看到或调用这些扩展函数。这包括 commit()
和 rollback()
的方法,它们现在只能从 class 本身内部调用,因为它们现在是 class 范围内的扩展函数。
由于接收到的 lambda 是 Transaction
的扩展函数,它在 class 的上下文中运行,因此可以看到扩展。 (参见:Function Literals with Receiver)
这段旧代码现在无效,编译器给我们一个错误:
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
上的扩展函数,因为它没有正式声明。
要在交易中将这些 "actions" 链接在一起,只需创建一系列可在交易中使用的扩展函数,例如:
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)
}
现在回到最初的问题,我们的交易方法和实体方法只在正确的时间出现。作为使用 lambda 或匿名函数的副作用,我们最终会探索有关代码组成方式的新想法。
请参阅
相关高级主题:
我们不会解决您可能 运行 到这里的所有问题。很容易让一些扩展函数出现在另一个 class 的上下文中。但是同时为两件事情做这项工作并不是那么容易。例如,如果我想让 Movie
方法 addActor()
只出现在 Transaction
块内,那就更难了。 addActor()
方法不能同时有两个接收者。所以我们要么有一个接收两个参数 Transaction.addActorToMovie(actor, movie)
的方法,要么我们需要另一个计划。
实现此目的的一种方法是使用我们可以扩展系统的中间对象。现在,下面的示例可能合理也可能不合理,但它显示了如何仅根据需要进行这种额外级别的公开功能。这是代码,我们在其中更改 Transaction
以实现接口 Transactable
以便我们现在可以随时 delegate to the interface 。
当我们添加新功能时,我们可以创建 Transactable
的新实现来公开这些函数并保存临时状态。然后一个简单的辅助函数可以很容易地访问这些隐藏的新 classes。所有添加都可以在不修改核心原classes.
核心classes:
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)
}
}
}
}
或者,如果您不介意它位于顶层而不是 class 中,那么整个事情可能只是 Transactable
上的顶层扩展函数包的命名空间。
有关使用中介 classes 的其他示例,请参阅:
- 在 Klutter TypeSafe 配置模块中,一个中间对象用于存储 "which property" 的状态,可以对其进行操作,因此它可以传递并改变其他可用的方法。
config.value("something").asString()
(code link) - 在 Klutter Netflix Graph 模块中,一个中间对象用于转换到 DSL 语法的另一部分
connect(node).edge(relation).to(otherNode)
。 (同一模块中的 code link) The test cases 显示更多用途,包括get()
和invoke()
等运算符如何仅在上下文中可用。