DynamicData: Koleksi Dinamis, Arsitektur MVVM, dan Ekstensi Reaktif

Februari 2019 menandai pelepasan ReactiveUI 9 - kerangka kerja lintas-platform untuk membangun aplikasi GUI pada platform Microsoft .NET. ReactiveUI adalah alat untuk integrasi ketat ekstensi reaktif dengan pola desain MVVM. Anda dapat membiasakan diri dengan kerangka kerja melalui serangkaian video atau halaman pembuka dokumentasi . Pembaruan ReactiveUI 9 mencakup banyak perbaikan dan peningkatan , tetapi mungkin yang paling penting dan menarik adalah integrasi dengan kerangka kerja DynamicData , memungkinkan Anda untuk bekerja dengan koleksi dinamis dalam mode Reaktif. Mari cari tahu apa yang bisa kita gunakan untuk DynamicData dan bagaimana kerangka kerja reaktif yang kuat ini bekerja di bawah tenda!


Pendahuluan


Pertama-tama mari kita tentukan kasus penggunaan untuk DynamicData dan mencari tahu apa yang tidak kita sukai tentang alat default untuk bekerja dengan dataset dinamis dari System.Collections.ObjectModel namespace.


Template MVVM, seperti yang kita ketahui, mengasumsikan pembagian tanggung jawab antara lapisan model, lapisan presentasi dan model presentasi aplikasi, juga dikenal sebagai model tampilan. Lapisan model diwakili oleh entitas domain dan layanan dan tidak tahu apa-apa tentang lapisan model tampilan. Model merangkum seluruh logika kompleks aplikasi, sementara model tampilan mendelegasikan operasi ke model, menyediakan akses ke informasi tentang keadaan aplikasi saat ini melalui properti yang dapat diamati, perintah, dan koleksi, ke tampilan. Alat default untuk bekerja dengan properti dinamis adalah antarmuka INotifyPropertyChanged , untuk bekerja dengan tindakan pengguna - ICommand , dan untuk bekerja dengan koleksi - antarmuka INotifyCollectionChanged , serta implementasi seperti, sebagai ObservableCollection<T> dan ReadOnlyObservableCollection<T> .




Implementasi dari INotifyPropertyChanged dan antarmuka ICommand biasanya ICommand pada pengembang dan kerangka kerja MVVM yang digunakan, tetapi menggunakan standar ObservableCollection<T> membebankan sejumlah batasan! Sebagai contoh, kami tidak dapat mengubah koleksi dari utas latar belakang tanpa Dispatcher.Invoke Panggilan atau panggilan serupa, dan itu akan sangat berguna untuk menyinkronkan array data dengan server melalui operasi latar belakang. Perlu dicatat, bahwa ketika menggunakan arsitektur MVVM yang bersih, lapisan model tidak boleh tahu apa-apa tentang kerangka kerja GUI yang digunakan, dan harus kompatibel dengan lapisan model dalam terminologi MVC atau MVP. Itu sebabnya banyak panggilan Dispatcher.Invoke Panggilan dalam layanan domain melanggar prinsip segregasi tanggung jawab.


Tentu saja, kami dapat mendeklarasikan suatu peristiwa dalam layanan domain, dan mengirimkan potongan dengan item yang diubah sebagai argumen peristiwa, kemudian berlangganan acara tersebut, merangkum Dispatcher.Invoke Menelepon memanggil balik antarmuka, sehingga aplikasi kami tidak akan bergantung pada GUI kerangka kerja, sebut antarmuka itu dari model tampilan dan modifikasi ObservableCollection<T> sesuai, tetapi ada banyak cara yang lebih elegan untuk menangani masalah seperti itu tanpa perlu menemukan kembali roda. Apa yang kita tunggu?


Ekstensi reaktif. Mengelola Streaming Data yang Dapat Diamati


Untuk sepenuhnya memahami abstraksi yang diperkenalkan oleh DynamicData , dan cara bekerja dengan mengubah set data reaktif, mari kita ingat apa itu pemrograman reaktif dan bagaimana menggunakannya dalam konteks platform Microsoft .NET dan pola desain MVVM . Cara mengatur interaksi antar komponen program dapat bersifat interaktif atau reaktif. Dengan pendekatan interaktif, konsumen menerima data dari produsen secara serempak (berbasis pull, T, IEnumerable), dan dengan pendekatan reaktif, produsen mendorong data ke konsumen secara tidak sinkron (berbasis push, Tugas, IObservable).




Pemrograman reaktif adalah pemrograman dengan aliran data asinkron, dan ekstensi reaktif adalah implementasi pemrograman reaktif, berdasarkan pada antarmuka IObserver dan IObserver dari namespace System, yang mendefinisikan serangkaian operasi mirip LINQ pada antarmuka IObservable , yang dikenal sebagai LINQ over Observable. Ekstensi reaktif mendukung .NET Standard dan berjalan di mana pun Microsoft .NET menjalankan.




ReactiveUI menawarkan pengembang aplikasi untuk mengambil keuntungan dari menggunakan implementasi reaktif untuk antarmuka ICommand dan INotifyPropertyChanged , dengan menyediakan alat-alat seperti ReactiveCommand<TIn, TOut> dan WhenAnyValue . WhenAnyValue memungkinkan Anda untuk mengonversi properti kelas yang mengimplementasikan antarmuka INotifyPropertyChanged ke aliran peristiwa tipe IObservable<T> , ini menyederhanakan penerapan properti dependen.


 public class ExampleViewModel : ReactiveObject { [Reactive] // Attribute from the ReactiveUI.Fody package, // takes care of aspect-oriented INPC implementation // for this particular property. public string Name { get; set; } public ExampleViewModel() { // Here we subscribe to OnPropertyChanged("Name") events. this.WhenAnyValue(x => x.Name) // IObservable<string> .Subscribe(Console.WriteLine); } } 

ReactiveCommand<TIn, TOut> memungkinkan Anda untuk bekerja dengan perintah seperti halnya dengan 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(); // Outputs: 42 

Hingga saat ini, kami telah bekerja dengan IObservable<T> , seperti peristiwa yang menerbitkan nilai baru tipe T setiap kali keadaan objek yang diamati berubah. Sederhananya, IObservable<T> adalah aliran peristiwa, kumpulan tipe T terbentang dalam waktu.


Tentu saja, kami dapat bekerja dengan koleksi dengan mudah dan alami - setiap kali koleksi berubah, kami dapat menerbitkan koleksi baru dengan elemen yang diubah. Dalam hal ini, nilai yang dipublikasikan akan bertipe IEnumerable<T> atau lebih terspesialisasi, dan stream yang dapat diamati itu sendiri bertipe IObservable<IEnumerable<T>> . Tetapi, seperti dicatat oleh pembaca yang berpikiran kritis, ini penuh dengan masalah kinerja kritis, 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 ekstensi reaktif saat bekerja dengan koleksi. Rx sangat kuat, tetapi di luar kotak tidak menyediakan apa pun untuk membantu mengelola koleksi, dan DynamicData memperbaikinya. Di sebagian besar aplikasi, ada kebutuhan untuk memperbarui koleksi secara dinamis - biasanya, koleksi diisi dengan item saat aplikasi dimulai, dan kemudian koleksi diperbarui secara tidak sinkron, menyinkronkan informasi dengan server atau database. Aplikasi modern cukup kompleks, dan seringkali perlu untuk membuat proyeksi koleksi - filter, transformasi, atau mengurutkan elemen. DynamicData dirancang untuk menghilangkan kode yang sangat rumit yang kita perlukan untuk mengelola set data yang berubah secara dinamis. Alat ini secara aktif mengembangkan dan memperbaiki, dan sekarang lebih dari 60 operator didukung untuk bekerja dengan koleksi.




DynamicData bukan merupakan implementasi alternatif dari ObservableCollection<T> . Arsitektur DynamicData didasarkan terutama pada konsep pemrograman berbasis domain. Ideologi penggunaan didasarkan pada fakta bahwa Anda mengontrol sumber data tertentu, kumpulan kode yang bertanggung jawab untuk menyinkronkan dan bermutasi data memiliki akses. Selanjutnya, Anda menerapkan serangkaian operator ke sumber data, dengan bantuan operator tersebut 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 - oleh karena itu, koleksi yang diwarisi akan selalu tetap disinkronkan dengan sumbernya.


Alih-alih IObservable<T> klasik IObservable<T> , DynamicData mendefinisikan operasi pada IObservable<IChangeSet<T>> dan IObservable<IChangeSet<TValue, TKey>> , di mana IChangeSet adalah bongkahan berisi informasi tentang perubahan koleksi, termasuk jenis perubahan dan elemen yang terpengaruh. Pendekatan ini dapat secara signifikan meningkatkan kinerja kode untuk bekerja dengan koleksi, ditulis secara reaktif. Anda selalu dapat mengubah IObservable<IChangeSet<T>> menjadi IObservable<IEnumerable<T>> , jika perlu mengakses semua elemen koleksi sekaligus. Jika ini terdengar sulit - jangan khawatir, contoh kode di bawah ini akan membuat semuanya jelas!


Data dinamis dalam aksi


Mari kita lihat beberapa contoh untuk lebih memahami bagaimana DynamicData bekerja, bagaimana ia berbeda dari System.Reactive dan tugas-tugas apa yang biasa diselesaikan oleh pengembang perangkat lunak GUI. Mari kita mulai dengan contoh komprehensif yang dipublikasikan di GitHub . Dalam contoh ini, sumber data adalah SourceCache<Trade, long> berisi kumpulan transaksi. Tujuannya adalah hanya menampilkan transaksi aktif, mengubah model menjadi objek proxy, mengurutkan koleksi.


 // The default collection from the System.Collections.ObjectModel // namespace, to which we bind XAML UI controls. ReadOnlyObservableCollection<TradeProxy> list; // The mutable data source, containing the list of transactions. // We can use Add, Remove, Insert and similar methods on it. var source = new SourceCache<Trade, long>(trade => trade.Id); var cancellation = source // Here we transform the data source to an observable change set. .Connect() // Now we have IObservable<IChangeSet<Trade, long>> here. // Filter only active transactions. .Filter(trade => trade.Status == TradeStatus.Live) // Transform the models into proxy objects. .Transform(trade => new TradeProxy(trade)) // No we have IObservable<IChangeSet<TrandeProxy, long>> // Order the trade proxies by timestamp. .Sort(SortExpressionComparer<TradeProxy> .Descending(trade => trade.Timestamp)) // Use the dispatcher scheduler to update the GUI. .ObserveOnDispatcher() // Bind the sorted objects to the collection from the // System.Collections.ObjectModel namespace. .Bind(out list) // Ensure that when deleting elements from the // collections, the resources will get disposed. .DisposeMany() .Subscribe(); 

Dalam contoh di atas, ketika mengubah SourceCache yang merupakan sumber data, ReadOnlyObservableCollection juga berubah. Pada saat yang sama, ketika menghapus item dari koleksi, metode Dispose akan dipanggil, koleksi akan selalu diperbarui hanya dari utas GUI dan akan tetap disortir dan disaring. Keren, sekarang kami tidak punya Dispatcher.Invoke Panggilan telepon dan kode sederhana dan mudah dibaca!


Sumber Data. SourceList dan SourceCache


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


 public class BackgroundService : IBackgroundService { // Declare the mutable data source containing trades. private readonly SourceList<Trade> _trades; // Expose the observable change set to the outside world. // If we have more than one subscriber, it is recommended // to use the Publish() operator from reactive extensions. public IObservable<IChangeSet<Trade>> Connect() => _trades.Connect(); public BackgroundService() { _trades = new SourceList<Trade>(); _trades.Add(new Trade()); // Mutate the source list! // Even from the background thread. } } 

Dengan bantuan operator DynamicData yang kuat, kita dapat mengubah IObservable<IChangeSet<Trade>> menjadi ReadOnlyObservableCollection dideklarasikan dalam model tampilan kami.


 public class TradesViewModel : ReactiveObject { private readonly ReadOnlyObservableCollection<TradeVm> _trades; public ReadOnlyObservableCollection<TradeVm> Trades => _trades; public TradesViewModel(IBackgroundService background) { // Connect to the data source, transform elements, bind // them to the read-only observable collection. background.Connect() .Transform(x => new TradeVm(x)) .ObserveOn(RxApp.MainThreadScheduler) .Bind(out _trades) .DisposeMany() .Subscribe(); } } 

Selain operator Transform , Filter dan Sort , DynamicData mendukung pengelompokan, operasi logis, perataan koleksi, penggunaan fungsi agregat, penghapusan elemen identik, penghitungan elemen, dan bahkan virtualisasi pada level model tampilan. Anda dapat membaca lebih lanjut tentang semua operator di README proyek di GitHub .




Selain dari SourceList dan SourceCache , pustaka DynamicData menyertakan implementasi koleksi yang dapat berulir tunggal - ObservableCollectionExtended . Untuk menyinkronkan dua koleksi dalam model tampilan Anda, nyatakan salah satunya sebagai ObservableCollectionExtended , dan yang lainnya sebagai ReadOnlyObservableCollection , dan kemudian gunakan operator ToObservableChangeSet , yang melakukan hal yang hampir sama dengan Connect, tetapi dimaksudkan untuk bekerja dengan ObservableCollection .


 // Declare the derived collection. ReadOnlyObservableCollection<TradeVm> _derived; // Declare and initialize the source collection. 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 menerima pemberitahuan setiap kali properti berubah, gunakan operator AutoRefresh dan berikan pemilih properti yang diperlukan. AutoRefresh dan operator DynamicData lainnya memungkinkan Anda untuk dengan mudah memvalidasi sejumlah besar formulir dan formulir bersarang yang ditampilkan di layar!


 // IObservable<bool> var isValid = databases .ToObservableChangeSet() // Subscribe only to IsValid property changes. .AutoRefresh(database => database.IsValid) // Materialize the collection. .ToCollection() // Determine if all forms are valid. .Select(x => x.All(y => y.IsValid)); // If ReactiveUI is used, you can transform the // IObservable<bool> variable to a property declared // as ObservableAsPropertyHelper<bool>, eg IsValid. _isValid = isValid .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.IsValid); 

Anda dapat membuat UI kompleks menggunakan fungsionalitas DynamicData, dan ini sangat relevan untuk sistem yang menampilkan sejumlah besar data secara real time, seperti aplikasi pesan instan dan sistem pemantauan.





Kesimpulan


ReactiveX adalah alat yang ampuh yang memungkinkan Anda untuk bekerja dengan aliran acara dan dengan UI, menulis kode portabel dan dapat dipelihara dan menyelesaikan tugas-tugas kompleks dengan cara yang sederhana dan elegan. ReactiveUI memungkinkan para pengembang .NET untuk mengintegrasikan ekstensi reaktif ke dalam proyek mereka menggunakan arsitektur MVVM dengan implementasi reaktif dari INotifyPropertyChanged dan ICommand , sementara DynamicData menangani manajemen pengumpulan dengan menerapkan INotifyCollectionChanged , memperluas kemampuan ekstensi reaktif dengan fokus pada kinerja.


Pustaka ReactiveUI dan DynamicData sepenuhnya kompatibel dengan semua kerangka kerja GUI pada platform .NET, termasuk Windows Presentation Foundation, Universal Windows Platform, Avalonia , Xamarin. Android, Formulir Xamarin, dan Xamarin iOS. Anda bisa mulai mempelajari DynamicData pada halaman yang sesuai dari dokumentasi ReactiveUI . Juga berhati-hatilah untuk membiasakan diri Anda dengan proyek Cuplikan DynamicData , yang berisi sampel kode untuk hampir semua yang Anda butuhkan.

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


All Articles