Properti Terdelegasi di Kotlin

1. Perkenalan

Bahasa pemrograman Kotlin memiliki dukungan asli untuk properti kelas.

Properti biasanya didukung langsung oleh kolom terkait, tetapi tidak selalu harus seperti ini - selama properti tersebut diekspos dengan benar ke dunia luar, properti tersebut masih dapat dianggap sebagai properti.

Ini dapat dicapai dengan menangani ini di pengambil dan penyetel, atau dengan memanfaatkan kekuatan Delegasi.

2. Apakah Properti yang Didelegasikan Itu?

Sederhananya, properti yang didelegasikan tidak didukung oleh bidang kelas dan mendelegasikan pengambilan dan pengaturan ke bagian kode lain. Hal ini memungkinkan fungsionalitas yang didelegasikan untuk diabstraksi dan dibagikan di antara beberapa properti serupa - misalnya menyimpan nilai properti di peta, bukan di bidang terpisah.

Properti yang didelegasikan digunakan dengan mendeklarasikan properti dan delegasi yang digunakannya. Kata kunci by menunjukkan bahwa properti dikontrol oleh delegasi yang disediakan, bukan bidangnya sendiri.

Sebagai contoh:

class DelegateExample(map: MutableMap) { var name: String by map }

Ini menggunakan fakta bahwa MutableMap itu sendiri adalah sebuah delegasi, memungkinkan Anda untuk memperlakukan kuncinya sebagai properti.

3. Properti Delegasi Standar

Pustaka standar Kotlin hadir dengan sekumpulan delegasi standar yang siap digunakan.

Kami telah melihat contoh penggunaan MutableMap untuk mendukung properti yang bisa berubah. Dengan cara yang sama, Anda dapat mendukung properti yang tidak dapat diubah menggunakan Peta - memungkinkan setiap bidang diakses sebagai properti, tetapi tidak pernah mengubahnya.

The malas delegasi memungkinkan nilai properti yang akan dihitung hanya pada akses pertama dan kemudian cache. Ini dapat berguna untuk properti yang mungkin mahal untuk dihitung dan yang mungkin tidak Anda perlukan - misalnya, dimuat dari database:

class DatabaseBackedUser(userId: String) { val name: String by lazy { queryForValue("SELECT name FROM users WHERE userId = :userId", mapOf("userId" to userId) } }

The diamati delegasi memungkinkan untuk lambda yang akan dipicu setiap saat nilai perubahan properti , misalnya memungkinkan untuk pemberitahuan perubahan atau memperbarui properti terkait lainnya:

class ObservedProperty { var name: String by Delegates.observable("") { prop, old, new -> println("Old value: $old, New value: $new") } }

Mulai Kotlin 1.4, juga dimungkinkan untuk mendelegasikan secara langsung ke properti lain. Misalnya, jika kita mengganti nama properti di kelas API, kita dapat meninggalkan yang lama di tempatnya dan mendelegasikan ke yang baru:

class RenamedProperty { var newName: String = "" @Deprecated("Use newName instead") var name: String by this::newName }

Di sini, setiap kali kita mengakses properti name , kita secara efektif menggunakan properti newName sebagai gantinya.

4. Membuat Delegasi Anda

Akan ada saat-saat Anda ingin menulis delegasi Anda, daripada menggunakan yang sudah ada. Ini bergantung pada penulisan kelas yang memperluas salah satu dari dua antarmuka - ReadOnlyProperty atau ReadWriteProperty.

Kedua antarmuka ini mendefinisikan metode yang disebut getValue - yang digunakan untuk menyediakan nilai saat ini dari properti yang didelegasikan saat dibaca. Ini mengambil dua argumen dan mengembalikan nilai properti:

  • thisRef - referensi ke kelas tempat properti itu berada
  • properti - deskripsi refleksi dari properti yang didelegasikan

The ReadWriteProperty antarmuka tambahan mendefinisikan sebuah metode yang disebut setValue yang digunakan untuk memperbarui nilai saat ini dari properti ketika ditulis. Ini membutuhkan tiga argumen dan tidak memiliki nilai kembali:

  • thisRef - Referensi ke kelas tempat properti itu berada
  • properti - Deskripsi refleksi dari properti yang didelegasikan
  • nilai - Nilai baru properti

Mulai Kotlin 1.4, antarmuka ReadWriteProperty sebenarnya memperluas ReadOnlyProperty. Ini memungkinkan kita untuk menulis satu kelas delegasi yang mengimplementasikan ReadWriteProperty dan menggunakannya untuk kolom hanya-baca dalam kode kita. Sebelumnya, kami harus menulis dua delegasi yang berbeda - satu untuk bidang hanya-baca dan satu lagi untuk bidang yang dapat diubah.

Sebagai contoh, mari kita menulis delegasi yang selalu bekerja terkait koneksi database, bukan bidang lokal:

class DatabaseDelegate(readQuery: String, writeQuery: String, id: Any) : ReadWriteDelegate { fun getValue(thisRef: R, property: KProperty): T { return queryForValue(readQuery, mapOf("id" to id)) } fun setValue(thisRef: R, property: KProperty, value: T) { update(writeQuery, mapOf("id" to id, "value" to value)) } }

Ini bergantung pada dua fungsi tingkat atas untuk mengakses database:

  • queryForValue - ini membutuhkan beberapa SQL dan beberapa mengikat dan mengembalikan nilai pertama
  • update - ini membutuhkan beberapa SQL dan beberapa mengikat dan memperlakukannya sebagai pernyataan UPDATE

Kami kemudian dapat menggunakan ini seperti delegasi biasa dan membuat kelas kami secara otomatis didukung oleh database:

class DatabaseUser(userId: String) { var name: String by DatabaseDelegate( "SELECT name FROM users WHERE userId = :id", "UPDATE users SET name = :value WHERE userId = :id", userId) var email: String by DatabaseDelegate( "SELECT email FROM users WHERE userId = :id", "UPDATE users SET email = :value WHERE userId = :id", userId) }

5. Mendelegasikan Pembuatan Delegasi

Fitur baru lainnya yang kami miliki di Kotlin 1.4 adalah kemampuan untuk mendelegasikan pembuatan kelas delegasi kami ke kelas lain. Ini bekerja dengan mengimplementasikan antarmuka PropertyDelegateProvider , yang memiliki metode tunggal untuk membuat instance sesuatu untuk digunakan sebagai delegasi sebenarnya.

Kita dapat menggunakan ini untuk menjalankan beberapa kode seputar pembuatan delegasi yang akan digunakan - misalnya, untuk mencatat apa yang terjadi. Kita juga dapat menggunakannya untuk secara dinamis memilih delegasi yang akan kita gunakan berdasarkan properti yang akan digunakan. Misalnya, kita mungkin memiliki delegasi yang berbeda jika propertinya nullable:

class DatabaseDelegateProvider(readQuery: String, writeQuery: String, id: Any) : PropertyDelegateProvider
     
       { override operator fun provideDelegate(thisRef: T, prop: KProperty): ReadWriteDelegate { if (prop.returnType.isMarkedNullable) { return NullableDatabaseDelegate(readQuery, writeQuery, id) } else { return NonNullDatabaseDelegate(readQuery, writeQuery, id) } } }
     

Ini memungkinkan kami untuk menulis kode yang lebih sederhana di setiap delegasi karena mereka hanya perlu fokus pada kasus yang lebih bertarget. Di atas, kita tahu bahwa NonNullDatabaseDelegate hanya akan digunakan pada properti yang tidak dapat memiliki nilai null , jadi kita tidak memerlukan logika tambahan untuk menanganinya.

6. Ringkasan

Delegasi properti adalah teknik yang ampuh, yang memungkinkan Anda menulis kode yang mengambil alih kendali properti lain, dan membantu logika ini agar mudah dibagikan di antara kelas yang berbeda. Hal ini memungkinkan logika yang kuat dan dapat digunakan kembali yang terlihat dan terasa seperti akses properti biasa.

Contoh yang berfungsi penuh untuk artikel ini dapat ditemukan di GitHub.