Delegasi (delegation) meneruskan pelaksanaan sebuah kontrak ke objek lain. Kotlin mendukungnya sebagai bagian bahasa dengan kata kunci by, baik untuk delegasi implementasi interface antarkelas maupun untuk delegated property yang meneruskan akses getter dan setternya ke objek pengelola. Topik ini membahas delegasi implementasi dengan by, properti tertunda dengan lazy, serta pengawasan perubahan nilai dengan Delegates.observable dan Delegates.vetoable. Seluruh contoh dieksekusi dengan compiler Kotlin sehingga keluarannya berasal dari program yang benar-benar berjalan.
Delegasi implementasi dengan by#
Pola umumnya adalah sebuah kelas mengimplementasikan interface dengan meneruskan seluruh metodenya ke objek lain yang sudah menyediakan implementasinya. Tanpa dukungan bahasa, setiap metode harus ditulis ulang untuk sekadar meneruskan panggilan. Dengan by, satu baris deklarasi sudah cukup, dan compiler menghasilkan penerusan panggilan untuk setiap anggota interface yang tidak di-override.
import kotlin.properties.Delegates
interface Penyimpan {
fun simpan(data: String)
fun muat(): String
}
class PenyimpanMemori : Penyimpan {
private val isi = mutableListOf<String>()
override fun simpan(data: String) {
isi.add(data)
}
override fun muat(): String = isi.lastOrNull() ?: "(kosong)"
}
class Arsip(penyimpan: Penyimpan) : Penyimpan by penyimpan
class PelacakSuhu {
var suhu: Double by Delegates.observable(24.0) { _, lama, baru ->
println("suhu berubah dari $lama ke $baru")
}
var ambang: Int by Delegates.vetoable(100) { _, lama, usulan ->
println("usulan ambang: $lama ke $usulan")
usulan <= 200
}
}
val statistikKata: Map<String, Int> by lazy {
println("statistik dihitung")
mapOf("kotlin" to 3, "kode" to 5)
}
fun main() {
val arsip = Arsip(PenyimpanMemori())
arsip.simpan("pagi 24")
arsip.simpan("siang 29")
println(arsip.muat())
val pelacak = PelacakSuhu()
pelacak.suhu = 28.5
pelacak.ambang = 150
pelacak.ambang = 250
println(pelacak.ambang)
println(statistikKata["kotlin"])
println(statistikKata["kode"])
}# compiler Kotlin dan pustaka standar yang dipakai jalur ini
export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# kompilasi berkas main.kt menjadi out.jar, lalu jalankan programnya
kotlinc main.kt -d out.jar && java -cp "out.jar:$STDLIB" MainKtsiang 29
suhu berubah dari 24.0 ke 28.5
usulan ambang: 100 ke 150
usulan ambang: 150 ke 250
150
statistik dihitung
3
5Kelas Arsip dideklarasikan dengan Penyimpan by penyimpan, sehingga kedua metode interface diteruskan ke objek yang dikirim ke konstruktornya. Pada Arsip tidak ada satu pun badan simpan atau muat yang ditulis, tetapi arsip.simpan(...) tetap berfungsi dan arsip.muat() mengembalikan “siang 29”, yaitu data terakhir yang disimpan oleh PenyimpanMemori. Apabila satu metode perlu diubah, metode itu saja di-override pada Arsip, sedangkan metode lainnya tetap didelegasikan.
Secara mekanis, compiler menghasilkan metode penerusan untuk setiap anggota interface yang tidak di-override, dan badan metode itu hanya memanggil anggota yang sama pada objek penerimanya. Kelas pendelegasi dengan demikian tetap sah sebagai implementasi interface, sehingga objeknya dapat dikirim ke setiap tempat yang menuntut Penyimpan. Apabila kontrak interface bertambah, compiler memaksa kelas pendelegasinya ikut menyesuaikan, sama seperti kelas yang mengimplementasikan interface secara manual.
Delegated property dengan lazy#
Properti juga dapat didelegasikan, yaitu akses terhadapnya diteruskan ke objek pengelola yang menyediakan getValue dan setValue. Bentuk yang paling sering dipakai adalah by lazy, yaitu properti yang nilainya dihitung pada akses pertama, lalu hasilnya disimpan untuk seluruh akses berikutnya. Blok lazy cocok untuk perhitungan yang mahal dan belum tentu terpakai.
Pada main.kt, properti statistikKata dideklarasikan dengan by lazy dan bloknya mencetak “statistik dihitung” sebelum mengembalikan peta hasilnya. Dua akses berikutnya di bagian akhir main hanya membaca nilai yang tersimpan, sehingga teks “statistik dihitung” muncul satu kali saja pada keluaran, sedangkan nilai 3 dan 5 diperoleh dari peta yang sama. Blok lazy hanya dijalankan apabila properti benar-benar dibaca, sehingga perhitungannya tidak pernah terjadi bila akses tersebut tidak dilakukan. Secara mekanis, by lazy menghasilkan objek Lazy yang menyediakan getValue, dan perhitungannya dilindungi kunci (synchronized) secara bawaan sehingga aman dipanggil dari beberapa thread. Pola by lazy yang lebih rinci, termasuk perilaku pendaftarannya, sudah dibahas pada modul null safety jalur ini.
observable dan vetoable#
Fungsi Delegates.observable melaporkan setiap perubahan nilai setelah perubahan itu terjadi. Callback-nya menerima properti, nilai lama, dan nilai baru, lalu dijalankan setiap kali penugasan selesai. Pada contoh, penugasan pelacak.suhu = 28.5 memunculkan baris “suhu berubah dari 24.0 ke 28.5”, dengan 24.0 sebagai nilai bawaan propertinya.
Fungsi Delegates.vetoable bekerja lebih dahulu, yaitu sebelum nilai benar-benar berubah. Kembalian Boolean callback-nya memutuskan penugasan. Nilai bawaan ambang adalah 100, lalu usulan 150 diterima karena usulan <= 200 bernilai true. Usulan berikutnya bernilai 250 dan ditolak, sehingga baris “usulan ambang: 150 ke 250” hanya mencatat peristiwa penolakannya. Pemanggilan println(pelacak.ambang) menghasilkan 150, yaitu nilai lama yang tetap dipakai.
Kedua fungsi itu dibangun di atas mekanisme setValue yang sama dengan properti delegasi pada umumnya, sehingga keduanya dapat dipadukan dengan pola properti delegasi lain. Callback-nya menerima objek KProperty sebagai parameter pertama, yang pada contoh dibiarkan sebagai _ karena namanya tidak dipakai. Parameter kedua dan ketiganya adalah nilai lama serta nilai usulan yang menjadi dasar keputusan callback.
Ringkasan#
Penyimpan by penyimpanmeneruskan seluruh metode interface ke objek yang dipasangkan pada konstruktornya, dan metode tertentu masih boleh di-override.- Properti dapat didelegasikan dengan
by, danby lazymenghitung nilai pada akses pertama lalu menyimpannya untuk akses berikutnya. Delegates.observablemenjalankan callback setelah perubahan nilai, sedangkanDelegates.vetoablememutuskan perubahan sebelum terjadi lewat kembalian Boolean.- Penolakan pada
vetoablemembatalkan penugasan tanpa error, sehingga nilai lama tetap dipakai.
Referensi#
- Delegation — Kotlin Documentation — Delegasi implementasi interface dengan kata kunci by pada dokumentasi resmi Kotlin.
- Delegated properties — Kotlin Documentation — lazy, Delegates.observable, dan Delegates.vetoable beserta contoh pemakaiannya.