在Kotlin中,如何将扩展方法添加到另一个类,但只在某个上下文中可见?

时间:2016-01-14 18:45:43

标签: dsl builder kotlin

在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来说是不准确的。其他答案也很受欢迎,有很多样式可以解答这个问题!

2 个答案:

答案 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之内,这样如果不在此类的上下文中,这些扩展函数将不会被看到也无法调用。这包括Transactioncommit()的方法,这些方法现在只能在类本身内调用,因为它们现在是类中的扩展函数。

由于收到的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上的顶级扩展函数,如果你不介意它处于顶级,而不是在类中,并且使包的命名空间变得混乱。

有关使用中间类的其他示例,请参阅:

  • 在Klutter TypeSafe配置模块中,中间对象用于存储&#34;哪个属性&#34;可以采取行动,因此它可以传递,并改变其他方法可用。 config.value("something").asString()code link
  • 在Klutter Netflix Graph模块中,中间对象用于转换到DSL语法connect(node).edge(relation).to(otherNode)的另一部分。 (code link)同一模块中的test cases会显示更多用途,包括get()invoke()等运算符仅在上下文中可用的情况。