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 には当てはまらないことを明確にしました。他の回答も歓迎します。この質問への回答方法にはさまざまなスタイルがあります。
ベストアンサー1
基礎:
Kotlin では、他のクラスに渡されるラムダを使用して、それらのクラスに「スコープ」を与えたり、ラムダの実行前後にエラー処理などの動作を実行したりする傾向があります。したがって、まず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() { ... }
}
ここでは、トランザクションを作成すると、トランザクション内で処理を行うラムダが必要になり、例外がスローされない場合はトランザクションが自動的にコミットされます。(クラスのコンストラクタはTransaction
、高階関数)
また、 の拡張関数をEntity
内に移動したTransaction
ので、これらの拡張関数はこのクラスのコンテキスト以外では表示も呼び出しもできません。これには と のメソッドが含まれます。これらcommit()
はrollback()
クラス内でスコープ設定された拡張関数になったため、クラス自体の内部からのみ呼び出すことができます。
受け取られるラムダは拡張関数であるため、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()
}
}
}
渡されるラムダはTransaction
正式な宣言がないため、拡張関数であると推測されます。
トランザクション内でこれらの「アクション」を連鎖させるには、トランザクション内で使用できる一連の拡張関数を作成します。次に例を示します。
fun Transaction.actsInMovie(actor: Person, film: Movie) {
film.addActor(actor)
save(film)
}
このようなものをさらに作成し、トランザクションに渡されるラムダでそれらを使用します...
Transaction {
actsInMovie(harrison, starWars)
actsInMovie(carrie, starWars)
directsMovie(abrams, starWars)
rateMovie(starWars, 5)
}
さて、元の質問に戻りますが、トランザクション メソッドとエンティティ メソッドは、適切なタイミングでのみ表示されます。また、ラムダまたは匿名関数を使用することの副作用として、コードの構成方法に関する新しいアイデアを探求することになります。