
Banyak hari ini suka pemrograman reaktif. Ini memiliki banyak keuntungan: kurangnya apa yang disebut "
neraka panggilan balik ", dan mekanisme penanganan kesalahan bawaan, dan gaya pemrograman fungsional yang mengurangi kemungkinan bug. Secara signifikan lebih mudah untuk menulis kode multi-utas dan lebih mudah untuk mengelola aliran data (menggabungkan, membagi, dan mengonversi).
Banyak bahasa pemrograman memiliki pustaka reaktif mereka sendiri: RxJava untuk JVM, RxJS untuk JavaScript, RxSwift untuk iOS, Rx.NET, dll.
Tapi apa yang kita miliki untuk Kotlin? Akan logis untuk menganggap RxKotlin itu. Dan, memang, perpustakaan seperti itu ada, tetapi itu hanya satu set ekstensi untuk RxJava2, yang disebut "gula".
Dan idealnya, saya ingin memiliki solusi yang memenuhi kriteria berikut:
- multi-platform - untuk dapat menulis perpustakaan multi-platform menggunakan pemrograman reaktif dan mendistribusikannya di dalam perusahaan;
- Keamanan kosong - sistem tipe Kotlin melindungi kita dari “ kesalahan miliar dolar ”, jadi nilai nol harus valid (misalnya, Dapat
Observable<String?>
);
- kovarians dan contravariance adalah fitur lain yang sangat berguna dari Kotlin, yang memungkinkan, misalnya, untuk secara aman memasukkan tipe
Observable<String>
ke Observable<CharSequence>
.
Kami di Badoo memutuskan untuk tidak menunggu cuaca di laut dan membuat perpustakaan seperti itu. Seperti yang mungkin sudah Anda duga, kami menyebutnya Reaktive dan mempostingnya di
GitHub .
Dalam artikel ini, kita akan melihat lebih dekat pada harapan pemrograman reaktif Kotlin dan melihat bagaimana kemampuan Reaktive cocok dengan mereka.
Tiga Manfaat Reaktif Alami
Multi-platform
Keuntungan
alami pertama adalah yang paling penting. Tim iOS, Android, dan Mobile Web kami saat ini ada secara terpisah. Persyaratannya bersifat umum, desainnya sama, tetapi masing-masing tim mengerjakan tugasnya sendiri.
Kotlin memungkinkan Anda untuk menulis kode multi-platform, tetapi Anda harus melupakan pemrograman reaktif. Dan saya ingin dapat menulis perpustakaan bersama menggunakan pemrograman reaktif dan mendistribusikannya di perusahaan atau mengunggah ke GitHub. Berpotensi, pendekatan ini dapat secara signifikan mengurangi waktu pengembangan dan mengurangi jumlah total kode.
Keamanan kosong
Ini lebih tentang kekurangan Java dan RxJava2. Singkatnya, null tidak dapat digunakan. Mari kita coba mencari tahu alasannya. Lihatlah antarmuka Java ini:
public interface UserDataSource { Single<User> load(); }
Bisakah hasilnya nol? Untuk menghindari ambiguitas, null tidak diizinkan di RxJava2. Dan jika Anda masih perlu, itu mungkin dan opsional. Tetapi di Kotlin tidak ada masalah seperti itu. Kita dapat mengatakan bahwa
Single<User>
dan
Single<User?>
tipe yang berbeda, dan semua masalah muncul pada tahap kompilasi.
Kovarian dan contravariance
Ini adalah fitur khas Kotlin, sesuatu yang sangat kurang di Jawa. Anda dapat membaca lebih lanjut tentang ini di
manual . Saya hanya akan memberikan beberapa contoh menarik tentang masalah apa yang muncul saat menggunakan RxJava di Kotlin.
Kovarian :
fun bar(source: Observable<CharSequence>) { } fun foo(source: Observable<String>) { bar(source)
Karena
Observable
adalah antarmuka Java, kode tersebut tidak dapat dikompilasi. Ini karena tipe generik di Jawa tidak tetap. Anda bisa, tentu saja, menggunakan, tetapi kemudian menggunakan operator seperti pemindaian lagi akan menyebabkan kesalahan kompilasi:
fun bar(source: Observable<out CharSequence>) { source.scan { a, b -> "$a,$b" }
Pernyataan pemindaian berbeda karena tipe generik "T" adalah input dan output. Jika Observable adalah antarmuka Kotlin, maka tipe T-nya dapat dilambangkan sebagai keluar dan ini akan menyelesaikan masalah:
interface Observable<out T> { … }
Dan berikut ini adalah contoh dengan contravariance:
fun bar(consumer: Consumer<String>) { } fun foo(consumer: Consumer<CharSequence>) { bar(consumer)
Untuk alasan yang sama seperti pada contoh sebelumnya (tipe generik di Java adalah invarian), contoh ini tidak dikompilasi. Menambahkan akan menyelesaikan masalah, tetapi sekali lagi tidak seratus persen:
fun bar(consumer: Consumer<in String>) { if (consumer is Subject) { val value: String = consumer.value
Nah, menurut tradisi, di Kotlin masalah ini diselesaikan dengan menggunakan di dalam antarmuka:
interface Consumer<in T> { fun accept(value: T) }
Dengan demikian, variabilitas dan contravariance dari tipe generik adalah keuntungan
alami ketiga dari perpustakaan Reaktive.
Kotlin + Reaktif = Reaktive
Kami beralih ke hal utama - deskripsi perpustakaan Reaktive.
Berikut adalah beberapa fitur-fiturnya:
- Ini adalah multi-platform, yang berarti Anda akhirnya dapat menulis kode umum. Di Badoo, kami menganggap ini salah satu manfaat paling penting.
- Ini ditulis dalam Kotlin, yang memberi kita keuntungan yang dijelaskan di atas: tidak ada batasan pada null, varians / contravariance. Ini meningkatkan fleksibilitas dan memberikan keamanan selama kompilasi.
- Tidak ada ketergantungan pada perpustakaan lain, seperti RxJava, RxSwift, dll., Yang berarti bahwa tidak perlu membawa fungsi perpustakaan ke penyebut umum.
- API murni. Misalnya, antarmuka
ObservableSource
di Reaktive hanya disebut Observable
, dan semua operator adalah fungsi ekstensi yang terletak di file terpisah. Tidak ada kelas Tuhan dari 15.000 baris. Ini memungkinkan untuk dengan mudah meningkatkan fungsionalitas tanpa membuat perubahan pada antarmuka dan kelas yang ada.
- Dukungan untuk penjadwal (menggunakan
subscribeOn
dikenal dan observeOn
).
- Kompatibel dengan RxJava2 (interoperabilitas), menyediakan konversi sumber antara Reaktive dan RxJava2 dan kemampuan untuk menggunakan kembali penjadwal dari RxJava2.
- Kepatuhan ReactiveX .
Saya ingin berbicara sedikit lebih banyak tentang manfaat yang telah kami terima karena fakta bahwa perpustakaan ditulis di Kotlin.
- Di Reaktive, nilai nol diizinkan, karena di Kotlin itu aman. Berikut ini beberapa contoh menarik:
observableOf<String>(null) //
val o1: Observable<String?> = observableOf(null)
val o2: Observable<String> = o1 // ,
val o1: Observable<String?> = observableOf(null)
val o2: Observable<String> = o1.notNull() // , null
val o1: Observable<String> = observableOf("Hello")
val o2: Observable<String?> = o1 //
val o1: Observable<String?> = observableOf(null)
val o2: Observable<String> = observableOf("Hello")
val o3: Observable<String?> = merge(o1, o2) //
val o4: Observable<String> = merge(o1, o2) // ,
Variasi juga merupakan keuntungan besar. Misalnya, dalam antarmuka yang Observable
, tipe T dinyatakan sebagai out
, yang memungkinkan untuk menulis sesuatu seperti berikut:
fun foo() { val source: Observable<String> = observableOf("Hello") bar(source)
Seperti inilah perpustakaan saat ini:- status pada saat penulisan: alpha (beberapa perubahan dalam API publik dimungkinkan);
- platform yang didukung: JVM dan Android;
- sumber yang didukung: Dapat
Observable
, Maybe
, Single
dan Completable
;
- sejumlah besar operator didukung, termasuk peta, filter, flatMap, concatMap, CombatLatest, zip, merge dan lainnya (daftar lengkap dapat ditemukan di GitHub );
- Penjadwal berikut ini didukung: komputasi, IO, trampolin dan utama;
- subyek: PublishSubject dan BehaviorSubject;
- backpressure belum didukung, tetapi kami berpikir tentang perlunya dan implementasi fitur ini.
Apa rencana kami untuk waktu dekat:- mulai menggunakan Reaktive dalam produk kami (kami sedang mempertimbangkan opsi);
- Dukungan JavaScript (permintaan tarik sudah ditinjau);
- Dukungan iOS
- menerbitkan artefak di JCenter (saat ini menggunakan layanan JitPack);
- Dokumentasi
- peningkatan jumlah operator yang didukung;
- Tes
- lebih banyak platform - permintaan tarik dipersilahkan!
Anda dapat mencoba perpustakaan sekarang, Anda dapat menemukan semua yang Anda butuhkan di
GitHub . Bagikan pengalaman Anda dan ajukan pertanyaan. Kami akan berterima kasih atas umpan baliknya.