DynamicData: المجموعات الديناميكية وهندسة MVVM والتمديدات التفاعلية

تميز فبراير 2019 بإصدار ReactiveUI 9 - إطار عمل النظام الأساسي لبناء تطبيقات واجهة المستخدم الرسومية على نظام Microsoft .NET الأساسي. ReactiveUI هي أداة للتكامل المحكم للامتدادات التفاعلية مع نمط تصميم MVVM. يمكنك التعرف على الإطار عبر سلسلة من مقاطع الفيديو أو صفحة الترحيب بالوثائق . يتضمن تحديث ReactiveUI 9 العديد من الإصلاحات والتحسينات ، ولكن ربما كان الأكثر أهمية وإثارة للاهتمام هو التكامل مع إطار DynamicData ، مما يتيح لك العمل مع المجموعات الديناميكية بأسلوب Reactive. دعونا معرفة ما يمكننا استخدام DynamicData وكيف يعمل هذا الإطار التفاعلي القوي تحت غطاء محرك السيارة!


مقدمة


دعنا أولاً نحدد حالات الاستخدام لـ DynamicData ومعرفة ما لا يعجبنا حول الأدوات الافتراضية للعمل مع مجموعات البيانات الديناميكية من مساحة الاسم System.Collections.ObjectModel .


قالب MVVM ، كما نعلم ، يفترض تقسيم المسؤولية بين طبقة النموذج وطبقة العرض التقديمي ونموذج عرض التطبيق ، المعروف أيضًا باسم طراز العرض. يتم تمثيل طبقة النموذج بواسطة كيانات المجال والخدمات ولا يعرف أي شيء عن طبقة نموذج العرض. يقوم النموذج بتغليف المنطق المعقد للتطبيق بأكمله ، بينما يفوض نموذج العرض العمليات إلى النموذج ، مما يوفر الوصول إلى المعلومات عن الحالة الحالية للتطبيق من خلال الخصائص والأوامر والمجموعات القابلة للملاحظة ، إلى العرض. الأداة الافتراضية للعمل مع الخصائص الديناميكية هي واجهة INotifyPropertyChanged ، للعمل مع إجراءات المستخدم - ICommand ، INotifyCollectionChanged مع المجموعات - واجهة INotifyCollectionChanged ، وكذلك مثل هذه التطبيقات ، مثل ObservableCollection<T> و ReadOnlyObservableCollection<T> .




عادة ما تكون تطبيقات واجهات INotifyPropertyChanged و ICommand تصل إلى المطور وإطار عمل MVVM المستخدم ، ولكن باستخدام ObservableCollection الافتراضي ObservableCollection<T> يفرض عددًا من القيود! على سبيل المثال ، لا يمكننا تحويل المجموعة من مؤشر ترابط الخلفية دون Dispatcher.Invoke أو مكالمة مماثلة ، وكان ذلك مفيدًا جدًا لمزامنة صفائف البيانات مع الخادم عبر عملية في الخلفية. تجدر الإشارة إلى أنه عند استخدام بنية MVVM النظيفة ، يجب ألا تعرف طبقة النموذج أي شيء عن إطار واجهة المستخدم الرسومية المستخدمة ، ويجب أن تكون متوافقة مع طبقة النموذج في مصطلحات MVC أو MVP. هذا هو السبب في أن العديد من المكالمات Dispatcher.Invoke في مجال الخدمات تنتهك مبدأ الفصل بين المسؤولية.


بالطبع ، يمكن أن نعلن عن حدث في خدمة مجال ، وننقل مجموعة بالعناصر التي تم تغييرها كحجج للأحداث ، ثم اشترك في الحدث ، وقم بتغليف Dispatcher.Invoke استدعاء خلف واجهة ، لذلك لن يعتمد تطبيقنا على أي واجهة المستخدم الرسومية في إطار العمل ، اتصل بهذه الواجهة من طراز العرض وقم بتعديل ObservableCollection<T> وفقًا لذلك ، ولكن هناك طريقة أكثر أناقة للتعامل مع مثل هذه المشكلات دون الحاجة إلى إعادة اختراع العجلة. ما الذي ننتظره إذن؟


ملحقات رد الفعل. إدارة تدفقات البيانات التي يمكن ملاحظتها


لفهم التجريدات التي قدمتها DynamicData تمامًا ، وكيفية التعامل مع تغيير مجموعات البيانات التفاعلية ، دعنا نتذكر ماهية البرمجة التفاعلية وكيفية استخدامها في سياق النظام الأساسي لـ Microsoft .NET ونمط تصميم MVVM . يمكن أن تكون طريقة تنظيم التفاعل بين مكونات البرنامج تفاعلية أو تفاعلية. من خلال النهج التفاعلي ، يتلقى المستهلك بيانات من المنتج بشكل متزامن (يعتمد على السحب ، T ، IEnumerable) ، ومع النهج التفاعلي ، يدفع المنتج البيانات إلى المستهلك بشكل غير متزامن (يعتمد على الضغط ، Task ، IObservable).




البرمجة التفاعلية هي البرمجة مع تدفقات البيانات غير المتزامنة ، والإضافات التفاعلية هي تنفيذ البرمجة التفاعلية ، بناءً على واجهات IObserver و IObserver من مساحة اسم النظام ، وتحديد سلسلة من عمليات LINQ المشابهة على واجهة IObservable ، والمعروفة باسم LINQ over Observable. تدعم الملحقات التفاعلية .NET Standard ويتم تشغيلها أينما كانت Microsoft .NET.




يوفر ReactiveUI مطوري التطبيقات للاستفادة من استخدام التطبيقات التفاعلية INotifyPropertyChanged ICommand و INotifyPropertyChanged ، من خلال توفير أدوات مثل ReactiveCommand<TIn, TOut> و WhenAnyValue . WhenAnyValue يسمح لك WhenAnyValue بتحويل خاصية لفئة تقوم بتنفيذ واجهة INotifyPropertyChanged إلى دفق حدث من النوع IObservable<T> ، فإن ذلك يبسط تطبيق الخصائص التابعة.


 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> العمل باستخدام أمر كما هو الحال مع IObservable<TOut> ، والذي يتم نشره عندما يكمل أمر التنفيذ. أيضًا ، يحتوي أي أمر على خاصية ThrownExceptions من النوع 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 

حتى هذا الوقت ، كنا نعمل مع IObservable<T> ، كما هو الحال مع حدث ينشر قيمة جديدة من النوع T كلما تغيرت حالة الكائن الذي تتم مراقبته. ببساطة ، IObservable<T> هو دفق من الأحداث ، عبارة عن مجموعة من النوع T تمتد في الوقت المناسب.


بالطبع ، يمكننا العمل مع المجموعات بنفس السهولة والطبيعية - كلما تغيرت المجموعة ، يمكننا نشر مجموعة جديدة تحتوي على عناصر تم تغييرها. في هذه الحالة ، ستكون القيمة المنشورة من النوع IEnumerable<T> أو أكثر تخصصًا ، وسيكون الدفق الملحوظ نفسه من النوع IObservable<IEnumerable<T>> . ولكن ، كما يلاحظ القارئ ذو التفكير النقدي بشكل صحيح ، فإن هذا محفوف بمشاكل الأداء الحرجة ، خاصة إذا لم يكن هناك أكثر من عشرة عناصر في مجموعتنا ، ولكن هناك مئات ، أو حتى بضعة آلاف!


مقدمة في DynamicData


DynamicData هي مكتبة تتيح لك استخدام قوة الملحقات التفاعلية عند العمل مع المجموعات. Rx قوية للغاية ، ولكن من خارج الصندوق لا يقدم أي شيء للمساعدة في إدارة المجموعات ، وتقوم DynamicData بإصلاح ذلك. في معظم التطبيقات ، هناك حاجة إلى تحديث المجموعات ديناميكيًا - عادةً ما يتم ملء المجموعة بالعناصر عند بدء تشغيل التطبيق ، ثم يتم تحديث المجموعة بشكل غير متزامن ، ومزامنة المعلومات مع خادم أو قاعدة بيانات. التطبيقات الحديثة معقدة للغاية ، وغالبًا ما يكون من الضروري إنشاء توقعات للمجموعات - تصفية العناصر أو تحويلها أو فرزها. تم تصميم DynamicData للتخلص من التعليمات البرمجية المعقدة بشكل لا يصدق التي سنحتاج إليها لإدارة مجموعات البيانات المتغيرة ديناميكيًا. تعمل الأداة على التطوير والتحسين بشكل نشط ، والآن يتم دعم أكثر من 60 مشغلًا للعمل مع المجموعات.




DynamicData ليس تطبيقًا بديلًا لـ ObservableCollection<T> . تعتمد بنية DynamicData أساسًا على مفاهيم البرمجة المعتمدة على المجال. تستند أيديولوجية الاستخدام إلى حقيقة أنك تتحكم في مصدر بيانات معين ، وهي مجموعة يمكن الوصول إليها من خلال الكود المسؤول عن مزامنة البيانات وتحويرها. بعد ذلك ، يمكنك تطبيق سلسلة من العوامل على مصدر البيانات ، وبمساعدة هؤلاء المشغلين ، يمكنك تحويل البيانات بشكل تعريفي دون الحاجة إلى إنشاء مجموعات أخرى وتعديلها يدويًا. في الواقع ، مع DynamicData تفصل بين عمليات القراءة والكتابة ، ويمكنك فقط القراءة بطريقة رد الفعل - لذلك ، ستظل المجموعات الموروثة دائمًا متزامنة مع المصدر.


بدلاً من IObservable<T> الكلاسيكية IObservable<T> ، تحدد DynamicData العمليات على IObservable<IChangeSet<T>> و IObservable<IChangeSet<TValue, TKey>> ، حيث IChangeSet عبارة عن قطعة تحتوي على معلومات حول تغيير المجموعة ، بما في ذلك نوع التغيير والعناصر المتأثرة. يمكن لهذا النهج تحسين أداء الكود بشكل كبير للعمل مع المجموعات ، المكتوبة بطريقة تفاعلية. يمكنك دائمًا تحويل IObservable<IChangeSet<T>> إلى IObservable<IEnumerable<T>> ، إذا أصبح من الضروري الوصول إلى جميع عناصر المجموعة في وقت واحد. إذا كان هذا يبدو صعبًا - لا تقلق ، فستوضح أمثلة التعليمات البرمجية أدناه كل شيء!


البيانات الديناميكية في العمل


دعونا نلقي نظرة على عدد من الأمثلة من أجل فهم أفضل لكيفية عمل DynamicData ، وكيف يختلف عن النظام. لنبدأ بمثال شامل منشور على جيثب . في هذا المثال ، يكون مصدر البيانات هو SourceCache<Trade, long> يحتوي على مجموعة من المعاملات. الهدف هو إظهار المعاملات النشطة فقط ، تحويل النماذج إلى كائنات وكيل ، فرز المجموعة.


 // 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(); 

في المثال أعلاه ، عند تغيير SourceCache الذي يعد مصدر البيانات ، تتغير ReadOnlyObservableCollection أيضًا وفقًا لذلك. في الوقت نفسه ، عند إزالة العناصر من المجموعة ، سيتم استدعاء طريقة Dispose ، سيتم دائمًا تحديث المجموعة فقط من سلسلة رسائل واجهة المستخدم الرسومية وستظل مرتبة ومُصفاة. رائع ، الآن ليس لدينا مكالمات Dispatcher.Invoke والرمز بسيط وقابل للقراءة!


مصادر البيانات. SourceList و SourceCache


توفر DynamicData مجموعتين متخصصتين يمكن استخدامهما كمصدر بيانات قابل للتغيير. هذه المجموعات هي SourceList<TObject> و SourceCache<TObject, TKey> . يوصى باستخدام SourceCache كلما كان مفتاح TObject فريدًا ، وإلا استخدم SourceList . توفر هذه الكائنات مألوفة لمطوري API API لإدارة المجموعة - مثل الأساليب ، Add ، Remove ، Insert . لتحويل مصادر البيانات إلى IObservable<IChangeSet<T>> أو إلى IObservable<IChangeSet<T, TKey>> ، استخدم عامل التشغيل IObservable<IChangeSet<T, TKey>> .Connect() . على سبيل المثال ، إذا كان لديك خدمة تقوم بتحديث مجموعة من العناصر في الخلفية بشكل دوري ، فيمكنك مزامنة قائمة هذه العناصر بسهولة مع واجهة المستخدم الرسومية ، دون Dispatcher.Invoke ورمز boilerplate مشابه:


 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. } } 

بمساعدة مشغلات DynamicData القوية ، يمكننا تحويل IObservable<IChangeSet<Trade>> إلى ReadOnlyObservableCollection إعلانه في نموذج العرض الخاص بنا.


 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(); } } 

بالإضافة إلى معاملات Transform و Filter و Sort ، تدعم DynamicData التجميع والعمليات المنطقية وتسوية المجموعات واستخدام الدوال التجميعية وإزالة العناصر المتطابقة وعد العناصر وحتى المحاكاة الافتراضية على مستوى طراز العرض. يمكنك قراءة المزيد حول جميع المشغلين في README للمشروع على جيثب .




بصرف النظر عن SourceList و SourceCache ، تتضمن مكتبة DynamicData تطبيق مجموعة قابلة للتغيير SourceCache واحد - ObservableCollectionExtended . لمزامنة مجموعتين في نموذج العرض الخاص بك ، قم بتعريف أحدهما على أنه ObservableCollectionExtended ، والآخر باسم ReadOnlyObservableCollection ، ثم استخدم عامل التشغيل ToObservableChangeSet ، الذي يقوم بنفس الشيء مثل Connect ، ولكن الغرض منه هو العمل مع 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 يدعم أيضًا تتبع التغيير في الفئات التي تقوم INotifyPropertyChanged واجهة INotifyPropertyChanged . على سبيل المثال ، إذا كنت ترغب في تلقي إعلامات في كل مرة تتغير فيها خاصية ، AutoRefresh عامل AutoRefresh وقم بتمرير محدد الخاصية المطلوب. يمكن أن يسمح لك AutoRefresh DynamicData الأخرى بالتحقق من صحة عدد هائل من النماذج والنماذج المتداخلة المعروضة على الشاشة!


 // 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); 

يمكنك إنشاء UIs معقدة باستخدام وظيفة DynamicData ، وهي ذات صلة بشكل خاص بالأنظمة التي تعرض كمية كبيرة من البيانات في الوقت الحقيقي ، مثل تطبيقات المراسلة الفورية وأنظمة المراقبة.





استنتاج


ReactiveX هي أداة قوية تتيح لك العمل مع تدفقات الأحداث ومع واجهة المستخدم ، وكتابة التعليمات البرمجية المحمولة وقابلة للصيانة وحل المهام المعقدة بطريقة بسيطة وأنيقة. يتيح ReactiveUI لمطوري .NET دمج ملحقات تفاعلية في مشاريعهم باستخدام بنية MVVM مع تطبيقات تفاعلية لـ INotifyPropertyChanged و ICommand ، في حين أن DynamicData تهتم بإدارة المجموعات من خلال تطبيق INotifyCollectionChanged ، مما يوسع من قدرات الامتدادات التفاعلية مع التركيز على الأداء.


مكتبات ReactiveUI و DynamicData متوافقة تمامًا مع جميع أطر عمل واجهة المستخدم الرسومية على النظام الأساسي .NET ، بما في ذلك Windows Presentation Foundation و Universal Windows Platform و Avalonia و Xamarin.Android و Xamarin Forms و Xamarin iOS. يمكنك البدء في دراسة DynamicData على الصفحة المقابلة لوثائق ReactiveUI . احرص أيضًا على التعرف على مشروع DynamicData Snippets ، الذي يحتوي على نماذج التعليمات البرمجية لكل ما قد تحتاج إليه تقريبًا.

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


All Articles