In Kotlin, I want to add extension methods to a class, for example to class Entity
. But I only want to see these extensions when Entity
is within
See the other answer for the main topic and the basics, here be deeper waters...
We do not solve everything you might run into here. It is easy to make some extension function appear in the context of another class. But it isn't so easy to make this work for two things at the same time. For example, if I wanted the Movie
method addActor()
to only appear while inside a Transaction
block, it is more difficult. The addActor()
method cannot have two receivers at the same time. So we either have a method that receives two parameters Transaction.addActorToMovie(actor, movie)
or we need another plan.
One way to do this is to use intermediary objects by which we can extend the system. Now, the following example may or may not be sensible, but it shows how to go this extra level of exposing functions only as desired. Here is the code, where we change Transaction
to implement an interface Transactable
so that we can now delegate to the interface whenever we want.
When we add new functionality we can create new implementations of Transactable
that expose these functions and also holds temporary state. Then a simple helper function can make it easy to access these hidden new classes. All additions can be done without modifying the core original classes.
Core 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 { ... }
Later, we decide to add:
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)
}
Now using the new functionality:
fun castChanges(swaps: Pair, film: Movie) {
Transaction {
forMovie(film) {
swaps.forEach {
// only available here inside forMovie() lambda
swapActor(it.first, it.second)
}
}
}
}
Or this whole thing could just have been a top level extension function on Transactable
if you didn't mind it being at the top level, not in a class, and cluttering up the namespace of the package.
For other examples of using intermediary classes, see:
config.value("something").asString()
(code link)connect(node).edge(relation).to(otherNode)
. (code link) The test cases in the same module show more uses including how even operators such as get()
and invoke()
are available only in context.