DynamicData: Mengubah koleksi, pola desain MVVM, dan ekstensi reaktif

Pada bulan Februari 2019, ReactiveUI 9 , kerangka kerja lintas platform untuk membangun aplikasi GUI pada platform Microsoft .NET, dirilis. ReactiveUI adalah alat untuk mengintegrasikan ekstensi reaktif dengan pola desain MVVM. Kenalan dengan kerangka kerja dapat dimulai dengan serangkaian artikel tentang HabrΓ© atau dari halaman depan dokumentasi . Pembaruan ReactiveUI 9 mencakup banyak perbaikan dan peningkatan , tetapi mungkin perubahan yang paling menarik dan signifikan adalah integrasi yang erat dengan kerangka kerja DynamicData , yang memungkinkan bekerja dengan mengubah koleksi dengan gaya reaktif. Mari kita coba mencari tahu dalam kasus apa DynamicData dapat berguna dan bagaimana kerangka reaktif yang kuat ini diatur di dalam!

Latar belakang


Pertama, kita mendefinisikan berbagai tugas yang dipecahkan DynamicData dan mencari tahu mengapa alat standar untuk bekerja dengan mengubah set data dari System.Collections.ObjectModel namespace tidak cocok untuk kita.

Template MVVM, seperti yang Anda ketahui, melibatkan pembagian tanggung jawab antara lapisan model, presentasi, dan model presentasi aplikasi. Lapisan model diwakili oleh entitas domain dan layanan, dan tidak tahu apa-apa tentang model presentasi. Lapisan model merangkum seluruh logika aplikasi kompleks, dan model presentasi mendelegasikan operasi model, memberikan tampilan akses ke informasi tentang keadaan aplikasi saat ini melalui properti yang dapat diamati, perintah, dan koleksi. Alat standar untuk bekerja dengan mengubah properti adalah antarmuka INotifyPropertyChanged , INotifyPropertyChanged untuk bekerja dengan tindakan pengguna, dan INotifyCollectionChanged mengimplementasikan koleksi dan mengimplementasikan ObservableCollection dan ReadOnlyObservableCollection .



Implementasi INotifyPropertyChanged dan ICommand biasanya tetap pada hati nurani pengembang dan kerangka kerja MVVM yang digunakan, tetapi penggunaan ObservableCollection memberlakukan sejumlah batasan pada kami! Misalnya, kami tidak dapat mengubah koleksi dari utas latar belakang tanpa Dispatcher.Invoke Panggilan atau panggilan serupa, dan ini bisa berguna jika bekerja dengan array data yang beberapa operasi latar belakang disinkronkan dengan server. Perlu dicatat bahwa dalam MVVM idiomatik, lapisan model tidak perlu tahu tentang arsitektur aplikasi GUI yang digunakan, dan kompatibel dengan model dari MVC atau MVP, dan inilah sebabnya banyak Dispatcher.Invoke memungkinkan akses ke kontrol antarmuka pengguna dari latar belakang thread running dalam layanan domain, melanggar prinsip berbagi tanggung jawab antara lapisan aplikasi.

Tentu saja, dalam layanan domain akan dimungkinkan untuk mendeklarasikan suatu peristiwa, dan sebagai argumen dari suatu peristiwa, memberikan potongan dengan data yang diubah. Kemudian berlangganan acara tersebut, bungkus Dispatcher.Invoke Panggilan di antarmuka sehingga tidak tergantung pada kerangka GUI yang digunakan, pindah Dispatcher.Invoke model presentasi dan ubah ObservableCollection yang Dapat ObservableCollection kebutuhan, namun ada cara yang jauh lebih sederhana dan lebih elegan untuk menyelesaikan berbagai tugas yang ditunjukkan tanpa harus menulis sepeda . Ayo mulai belajar!

Ekstensi reaktif. Kelola aliran data


Untuk pemahaman lengkap tentang abstraksi yang diperkenalkan oleh DynamicData dan prinsip-prinsip bekerja dengan mengubah dataset reaktif, mari kita ingat apa itu pemrograman reaktif dan bagaimana menerapkannya dalam konteks platform Microsoft .NET dan pola desain MVVM . Cara untuk mengatur interaksi antar komponen program dapat bersifat interaktif dan reaktif. Dalam interaksi interaktif, fungsi konsumen secara sinkron menerima data dari fungsi penyedia (pendekatan berbasis-tarik, T , IEnumerable ), dan dalam interaksi reaktif, fungsi konsumen secara tidak sinkron mengirimkan data ke fungsi konsumen (pendekatan berbasis-push, Task , IObservable ).



Pemrograman reaktif adalah pemrograman menggunakan aliran data asinkron, dan ekstensi reaktif adalah kasus khusus dari implementasinya, berdasarkan pada IObserver dan IObserver dari namespace Sistem, yang mendefinisikan sejumlah operasi mirip LINQ pada antarmuka IObservable , yang disebut LINQ over Observable. Ekstensi reaktif mendukung .NET Standard dan berfungsi di mana pun platform Microsoft .NET berfungsi.



Kerangka kerja ReactiveUI mengundang pengembang aplikasi untuk mengambil keuntungan dari implementasi reaktif ICommand dan INotifyPropertyChanged , menyediakan alat yang kuat seperti ReactiveCommand<TIn, TOut> dan WhenAnyValue . WhenAnyValue memungkinkan WhenAnyValue untuk mengkonversi properti kelas yang mengimplementasikan INotifyPropertyChanged menjadi aliran peristiwa tipe IObservable<T> , yang menyederhanakan penerapan properti dependen.

 public class ExampleViewModel : ReactiveObject { [Reactive] //  ReactiveUI.Fody,  // -  // OnPropertyChanged   Name. public string Name { get; set; } public ExampleViewModel() { //  OnPropertyChanged("Name"). this.WhenAnyValue(x => x.Name) //   IObservable<string> .Subscribe(Console.WriteLine); } } 

ReactiveCommand<TIn, TOut> memungkinkan Anda untuk bekerja dengan perintah tersebut, seperti kejadian tipe IObservable<TOut> , yang diterbitkan kapan pun perintah menyelesaikan eksekusi. Juga, perintah apa pun memiliki properti IObservable<Exception> tipe IObservable<Exception> .

 // ReactiveCommand<Unit, int> var command = ReactiveCommand.Create(() => 42); command //   IObservable<int> .Subscribe(Console.WriteLine); command .ThrownExceptions //   IObservable<Exception> .Select(exception => exception.Message) //   IObservable<string> .Subscribe(Console.WriteLine); command.Execute().Subscribe(); // : 42 

Selama ini kami bekerja dengan IObservable<T> , seperti peristiwa yang menerbitkan nilai baru tipe T setiap kali keadaan objek yang dipantau berubah. Sederhananya, IObservable<T> adalah aliran peristiwa, urutan membentang dari waktu ke waktu.

Tentu saja, kami dapat dengan mudah dan alami bekerja dengan koleksi - setiap kali koleksi berubah, publikasikan koleksi baru dengan elemen yang diubah. Dalam hal ini, nilai yang dipublikasikan akan bertipe IEnumerable<T> atau lebih terspesialisasi, dan event itu sendiri akan bertipe IObservable<IEnumerable<T>> . Tetapi, seperti yang ditunjukkan oleh pembaca yang berpikir kritis, ini penuh dengan masalah serius dengan kinerja aplikasi, terutama jika tidak ada selusin elemen dalam koleksi kami, tetapi seratus, atau bahkan beberapa ribu!

Pengantar DynamicData


DynamicData adalah perpustakaan yang memungkinkan Anda menggunakan kekuatan penuh ekstensi reaktif saat bekerja dengan koleksi. Ekstensi reaktif di luar kotak tidak menyediakan cara optimal untuk bekerja dengan mengubah kumpulan data, dan tugas DynamicData adalah memperbaikinya. Di sebagian besar aplikasi, ada kebutuhan untuk memperbarui koleksi secara dinamis - biasanya, koleksi diisi dengan beberapa elemen ketika aplikasi dimulai, dan kemudian diperbarui secara tidak sinkron, menyinkronkan informasi dengan server atau database. Aplikasi modern cukup kompleks, dan seringkali ada kebutuhan untuk membuat proyeksi koleksi - filter, ubah atau sortir elemen. DynamicData dirancang hanya untuk menghilangkan kode yang sangat rumit yang kita perlukan untuk mengelola set data yang berubah secara dinamis. Alat ini dikembangkan dan difinalisasi secara aktif, dan sekarang lebih dari 60 operator untuk bekerja dengan koleksi didukung.



DynamicData bukan merupakan implementasi alternatif dari ObservableCollection<T> . Arsitektur DynamicData didasarkan terutama pada konsep-konsep pemrograman khusus domain. Ideologi penggunaan didasarkan pada fakta bahwa Anda mengelola beberapa sumber data, kumpulan yang dapat diakses oleh kode yang bertanggung jawab untuk menyinkronkan dan mengubah data. Selanjutnya, Anda menerapkan sejumlah operator ke sumber, yang dengannya Anda dapat mengubah data secara deklaratif, tanpa perlu secara manual membuat dan memodifikasi koleksi lainnya. Bahkan, dengan DynamicData Anda memisahkan operasi baca dan tulis, dan Anda hanya bisa membaca dengan cara reaktif - karena itu, koleksi yang diwarisi akan selalu disinkronkan dengan sumbernya.

Alih-alih IObservable<T> klasik, DynamicData mendefinisikan operasi pada IObservable<IChangeSet<T>>> dan IObservable<IChangeSet<TValue, TKey>> , di mana IChangeSet adalah bongkahan berisi informasi tentang perubahan koleksi - jenis perubahan dan elemen yang terpengaruh. Pendekatan ini dapat secara signifikan meningkatkan kinerja kode untuk bekerja dengan koleksi yang ditulis dengan gaya reaktif. Pada saat yang sama, IObservable<IChangeSet<T>> selalu dapat diubah menjadi IObservable<IEnumerable<T>> biasa IObservable<IEnumerable<T>> jika menjadi perlu untuk memproses semua elemen koleksi sekaligus. Jika kedengarannya rumit - jangan khawatir, dari contoh kode semuanya akan menjadi jelas dan transparan!

Contoh DynamicData


Mari kita lihat serangkaian contoh untuk lebih memahami bagaimana DynamicData bekerja, perbedaannya dengan System.Reactive dan tugas apa yang dapat diselesaikan oleh pengembang perangkat lunak aplikasi biasa dengan GUI. Mari kita mulai dengan contoh komprehensif yang diposting oleh DynamicData di GitHub . Dalam contoh, sumber data adalah SourceCache<Trade, long> , yang berisi kumpulan transaksi. Tugasnya adalah hanya menampilkan transaksi aktif, mengubah model menjadi objek proxy, mengurutkan koleksi.

 //    System.Collections.ObjectModel, //       . ReadOnlyObservableCollection<TradeProxy> list; //   ,   . //   Add, Remove, Insert   . var source = new SourceCache<Trade, long>(trade => trade.Id); var cancellation = source //      . //   IObservable<IChangeSet<Trade, long>> .Connect() //       . .Filter(trade => trade.Status == TradeStatus.Live) //    -. //   IObservable<IChangeSet<TrandeProxy, long>> .Transform(trade => new TradeProxy(trade)) //    . .Sort(SortExpressionComparer<TradeProxy> .Descending(trade => trade.Timestamp)) //  GUI    . .ObserveOnDispatcher() //    - //    System.Collections.ObjectModel. .Bind(out list) // ,       //    . .DisposeMany() .Subscribe(); 

Dalam contoh di atas, ketika Anda mengubah SourceCache , yang merupakan sumber data, ReadOnlyObservableCollection juga ReadOnlyObservableCollection berubah. Dalam hal ini, saat menghapus elemen dari koleksi, metode Dispose akan dipanggil, koleksi akan selalu diperbarui hanya dalam aliran GUI dan tetap diurutkan dan difilter. Keren, tidak ada Dispatcher.Invoke Kode Dispatcher.Invoke dan rumit!

SourceList dan Sumber Data SourceCache


DynamicData menyediakan dua koleksi khusus yang dapat digunakan sebagai sumber data yang bisa berubah. Koleksi ini adalah tipe SourceList dan SourceCache<TObject, TKey> . Disarankan untuk menggunakan SourceCache setiap kali TObject memiliki kunci unik, jika tidak gunakan SourceList . Objek-objek ini menyediakan .NET developer API untuk memodifikasi data - Add , Remove , Insert dan sejenisnya. Untuk mengonversi sumber data ke IObservable<IChangeSet<T>> atau IObservable<IChangeSet<T, TKey>> , gunakan operator .Connect() . Misalnya, jika Anda memiliki layanan yang memperbarui koleksi elemen di latar belakang, Anda dapat dengan mudah menyinkronkan daftar elemen-elemen ini dengan GUI, tanpa Dispatcher.Invoke dan ekses arsitektur:

 public class BackgroundService : IBackgroundService { //    . private readonly SourceList<Trade> _trades; //     . //  ,     , //    Publish()  Rx. public IObservable<IChangeSet<Trade>> Connect() => _trades.Connect(); public BackgroundService() { _trades = new SourceList<Trade>(); _trades.Add(new Trade()); //    ! //    . } } 

DynamicData menggunakan tipe .NET bawaan untuk memetakan data ke dunia luar. Menggunakan operator DynamicData yang kuat, kita dapat mengubah IObservable<IChangeSet<Trade>> menjadi ReadOnlyObservableCollection model tampilan kami.

 public class TradesViewModel : ReactiveObject { private readonly ReadOnlyObservableCollection<TradeVm> _trades; public ReadOnlyObservableCollection<TradeVm> Trades => _trades; public TradesViewModel(IBackgroundService background) { //   ,  ,  //     System.Collections.ObjectModel. background.Connect() .Transform(x => new TradeVm(x)) .ObserveOn(RxApp.MainThreadScheduler) .Bind(out _trades) .DisposeMany() .Subscribe(); } } 

Selain Transform , Filter dan Sort , DynamicData mencakup sejumlah operator lain, mendukung pengelompokan, operasi logis, perataan koleksi, menggunakan fungsi agregasi, tidak termasuk elemen identik, elemen penghitungan, dan bahkan virtualisasi pada level model representasi. Baca lebih lanjut tentang semua operator di proyek README di GitHub .



Koleksi berulir tunggal dan ubah pelacakan


Selain SourceList dan SourceCache , pustaka DynamicData juga menyertakan implementasi single-threaded dari koleksi yang bisa diubah - ObservableCollectionExtended . Untuk menyinkronkan dua koleksi di model tampilan Anda, deklarasikan satu sebagai ObservableCollectionExtended dan yang lainnya sebagai ReadOnlyObservableCollection dan gunakan operator ToObservableChangeSet , yang berperilaku sama seperti Connect tetapi dirancang untuk bekerja dengan ObservableCollection .

 //   . ReadOnlyObservableCollection<TradeVm> _derived; //    -. var source = new ObservableCollectionExtended<Trade>(); source.ToObservableChangeSet(trade => trade.Key) .Transform(trade => new TradeProxy(trade)) .Filter(proxy => proxy.IsChecked) .Bind(out _derived) .Subscribe(); 

DynamicData juga mendukung pelacakan perubahan di kelas yang mengimplementasikan antarmuka INotifyPropertyChanged . Misalnya, jika Anda ingin diberitahu tentang perubahan koleksi setiap kali properti item berubah, gunakan AutoRefresh dan berikan pemilih properti yang diinginkan dengan argumen. AutoRefesh dan operator DynamicData lainnya akan memungkinkan Anda untuk dengan mudah dan alami memvalidasi sejumlah besar formulir dan sub-formulir yang ditampilkan di layar!

 //  IObservable<bool> var isValid = databases .ToObservableChangeSet() //      IsValid. .AutoRefresh(database => database.IsValid) //       . .ToCollection() // ,    . .Select(x => x.All(y => y.IsValid)); //   ReactiveUI, IObservable<bool> //     ObservableAsProperty. _isValid = isValid .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.IsValid); 

Berdasarkan fungsionalitas DynamicData, Anda dapat dengan cepat membuat antarmuka yang cukup kompleks - ini terutama berlaku untuk sistem yang menampilkan sejumlah besar data waktu nyata, sistem pengiriman pesan instan, dan sistem pemantauan.



Kesimpulan


Ekstensi reaktif adalah alat yang ampuh yang memungkinkan Anda bekerja secara deklaratif dengan data dan antarmuka pengguna, menulis kode portabel dan didukung, dan memecahkan masalah rumit dengan cara yang sederhana dan elegan. ReactiveUI memungkinkan pengembang .NET untuk secara erat mengintegrasikan ekstensi reaktif ke dalam proyek mereka menggunakan arsitektur MVVM dengan memberikan implementasi reaktif dari INotifyPropertyChanged dan ICommand , sementara DynamicData menangani sinkronisasi pengumpulan dengan menerapkan INotifyCollectionChanged , memperluas kemampuan ekstensi reaktif dan menjaga kinerja.

Pustaka ReactiveUI dan DynamicData kompatibel dengan kerangka kerja GUI paling populer dari platform .NET, termasuk Windows Presentation Foundation, Universal Windows Platform, Avalonia , Xamarin. Android, Formulir Xamarin, Xamarin.iOS. Anda dapat mulai mempelajari DynamicData dari halaman dokumentasi ReactiveUI yang sesuai . Pastikan juga untuk memeriksa proyek DynamicData Snippets , yang berisi contoh penggunaan DynamicData untuk semua kesempatan.

Source: https://habr.com/ru/post/id445098/


All Articles