Gesaan dan Konteks
Beberapa fungsi dalam perkhidmatan Kotlin memerlukan UserService, pencatat log (logger), atau konteks transaksi yang sama. Pasukan mahu mengurangkan penyaluran parameter yang berulang (parameter plumbing) tanpa menyembunyikan kebergantungan dalam pemboleh ubah global, dan ia mesti berhijrah secara beransur-ansur daripada context receiver kepada context parameter Kotlin 2.2. Terangkan semantik, tingkah laku di tapak panggilan (call-site), pengendalian keambiguaan, dan sempadannya. Sasaran ialah jurutera Kotlin/JVM; tiada rangka kerja DI yang diandaikan.
Perkara yang Dinilai oleh Penemu Duga
Jawapan yang kukuh menyatakan bahawa context parameter ialah kebergantungan masa kompilasi yang tersedia secara tersirat—bukan bekas masa jalanan (runtime container). Resolusi berlaku di tapak panggilan dengan mencari jenis yang sepadan dalam skop semasa; beberapa nilai yang serasi pada tahap yang sama adalah tidak jelas (ambiguous). Calon membezakan context receiver daripada context parameter, mengakui migrasi dan had ciri, serta menerangkan sebab parameter biasa selalunya lebih jelas pada sempadan awam.
Penjelasan untuk Ditanya Terlebih Dahulu
- Adakah kebergantungan merentasi API modul atau pustaka awam? Jika ya, parameter eksplisit lebih mudah ditemui, diuji dan dipanggil daripada bahasa lain.
- Adakah kebergantungan stabil di dalam DSL tempatan atau operasi transaksi? Jika ya, context parameter boleh menghapuskan penyaluran parameter yang berulang.
- Bolehkah dua pelaksanaan jenis yang sama berada dalam skop? Jika ya, reka bentuk argumen konteks dinamakan atau jenis pembungkus semantik (semantic wrapper types).
- Adakah projek sudah menggunakan context receiver? Jika ya, sahkan bendera pengkompil dan skop migrasi; kedua-dua mod tidak boleh dicampur dalam satu modul.
Rangka Kerja Jawapan 30 Saat
“Context parameter mengisytiharkan kebergantungan pada tandatangan fungsi manakala skop tapak panggilan membekalkannya. Pengkompil menyelesaikannya mengikut jenis; dua padanan pada tahap yang sama adalah ralat. Ia sesuai untuk DSL tempatan, transaksi atau konteks pengelogan dan mengelakkan penyaluran berulang. Untuk API awam, panggilan silang bahasa atau kebergantungan yang kerap diganti, saya mengekalkan parameter eksplisit. Semasa migrasi, saya mendayakan pilihan pengkompil yang sepadan dan memeriksa panggilan penerima serta kes peringkat kelas kerana kedua-dua ciri tidak boleh ditukar ganti.”
Langkah demi Langkah Mendalam
Isytiharkan kebergantungan, kemudian sediakan ia dalam skop:
interface UserService {
fun findUser(id: Int): String
}
context(users: UserService)
fun greeting(id: Int): String = "Hello ${users.findUser(id)}"
fun main() {
val service = object : UserService {
override fun findUser(id: Int) = "user-$id"
}
context(service) {
println(greeting(7))
}
}Tandatangan masih mengisytiharkan kebergantungan, jadi pemanggil boleh melihatnya; context(service) membekalkan nilai di tapak panggilan. Resolusi memadankan jenis dalam skop semasa, bukan nama pemboleh ubah. Jika serviceA dan serviceB daripada jenis serasi yang sama hadir pada tahap yang sama, penyusunan gagal dan bukannya memilih satu secara senyap. Gunakan argumen konteks eksplisit atau jenis pembungkus semantik apabila kedua-dua makna adalah sah.
Sempadan dengan parameter biasa adalah mengenai kepadatan maklumat. Jika DSL tempatan mempunyai banyak fungsi yang berkongsi satu konteks yang tidak boleh diubah (immutable), context parameter mengalih keluar penyaluran parameter yang berulang. Jika satu fungsi menggunakan kebergantungan sekali sahaja, parameter biasa adalah lebih jelas. API pustaka awam juga mesti mempertimbangkan panggilan Java, kebolehtemuan IDE dan dokumentasi yang dijana; semakin jauh sumber tersirat bergerak, semakin tinggi kos penyelenggaraan.
Migrasi daripada context receiver adalah lebih daripada sekadar penggantian kata kunci. JetBrains menyatakan bahawa context parameter memerlukan nama dan context receiver peringkat kelas tidak mempunyai padanan langsung. Modul tidak boleh mendayakan kedua-dua mod. Tukar pilihan pengkompil bagi setiap modul, kemudian periksa tapak panggilan, rujukan boleh panggil (callable references), dan penerima peringkat kelas. Dokumentasi rasmi juga menyenaraikan sekatan: pembina (constructors) tidak boleh mengisytiharkan context parameter, dan sifat konteks tidak boleh mempunyai medan sandaran (backing fields) atau pemula (initializers).
Parameter tanpa nama mengelakkan nama tempatan yang tidak digunakan tetapi masih mengambil bahagian dalam resolusi:
context(_: UserService)
fun logGreeting(id: Int) {
println(greeting(id))
}Peraturan keputusan ialah: “Gunakan konteks untuk keadaan kongsi tempatan yang stabil; gunakan parameter biasa atau DI eksplisit untuk kebergantungan rentas sempadan, penggantian eksplisit atau pelbagai pelaksanaan.” Jangan tukar context parameter menjadi service locator. Ujian masih menyediakan kebergantungan melalui skop tapak panggilan, dan objek luar masih memiliki jangka hayatnya.
Contoh Jawapan Berkualiti Tinggi
Context parameter ialah pengisytiharan masa kompilasi bagi kebergantungan tersirat. Fungsi tersebut menyertakan UserService dalam tandatangannya, manakala pemanggil membekalkan tika (instance) di dalam context(service); pengkompil mencari skop semasa mengikut jenis dan melaporkan keambiguaan apabila dua nilai sepadan. Saya akan menggunakannya untuk DSL tempatan, transaksi atau rantaian pengelogan yang berkongsi konteks yang stabil. Untuk API awam, kesalingoperasian Java, atau kebergantungan yang kerap diganti, saya akan mengekalkan parameter eksplisit atau DI. Semasa migrasi context-receiver, saya akan menukar pilihan pengkompil bagi setiap modul, memeriksa nama, rujukan boleh panggil dan penerima peringkat kelas, serta mengambil kira sekatan seperti pembina dan sifat medan sandaran.
Kesilapan Biasa
- Kesilapan → memanggil context parameter sebagai bekas DI masa jalanan → Sebab ia gagal: pengkompil menyelesaikannya di tapak panggilan → Pembetulan: terangkan skop, pemadanan jenis dan keambiguaan masa kompilasi.
- Kesilapan → menganggap nilai jenis yang sama dipilih mengikut nama pemboleh ubah → Sebab ia gagal: padanan pada tahap yang sama adalah tidak jelas (ambiguous) → Pembetulan: gunakan argumen konteks eksplisit atau jenis pembungkus semantik.
- Kesilapan → menggantikan
context receiverdengancontext parametersecara mekanikal → Sebab ia gagal: penamaan, kes peringkat kelas dan rujukan boleh panggil berbeza → Pembetulan: hijrah bagi setiap modul dan periksa setiap tapak panggilan. - Kesilapan → menjadikan setiap kebergantungan tersirat → Sebab ia gagal: kebolehtemuan sempadan awam menurun → Pembetulan: pastikan konteks kekal tempatan dan stabil, dan gunakan parameter biasa pada sempadan.
Soalan Susulan dan Maklum Balas
Susulan 1: Bagaimanakah anda menggantikan kebergantungan konteks dalam ujian?
Cipta fake atau stub yang melaksanakan antara muka yang sama, kemudian jalankan subjek di dalam context(fake) { ... }. Jika ujian kerap menukar pelaksanaan, parameter eksplisit biasanya lebih jelas dan menjadikan kebergantungan kelihatan dalam laporan ujian.
Susulan 2: Bagaimana jika dua perkhidmatan jenis yang sama mesti wujud bersama?
Perkenalkan jenis pembungkus semantik seperti PrimaryUserService dan AuditUserService, atau hantar argumen konteks eksplisit di tapak panggilan. Jangan bergantung pada susunan pengisytiharan kerana resolusi tidak menggunakan susunan sebagai keutamaan.
Susulan 3: Mengapakah pembina (constructor) tidak boleh mengisytiharkan context parameter?
Dokumentasi Kotlin semasa secara eksplisit menyekat pembina daripada mengisytiharkan context parameter. Untuk kebergantungan peringkat objek, gunakan parameter pembina biasa atau kilang (factory) eksplisit; memaksa konteks global akan menyembunyikan sempadan jangka hayat dan benang (thread).