DynamicData: تغيير المجموعات ونمط تصميم MVVM والملحقات التفاعلية

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

الشروط


أولاً ، نحدد نطاق المهام التي تحلها DynamicData ونكتشف لماذا لا تناسبنا الأدوات القياسية للعمل مع تغيير مجموعات البيانات من مساحة الاسم System.Collections.ObjectModel .

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



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

بالطبع ، في خدمة المجال سيكون من الممكن إعلان حدث ، وكحجة لحدث ما ، تمرير قطعة مع البيانات التي تم تغييرها. ثم قم بالاشتراك في الحدث ، قم بلف Dispatcher.Invoke استدعاء في واجهة بحيث لا يعتمد على إطار واجهة المستخدم الرسومية المستخدم ، انقل Dispatcher.Invoke إلى نموذج العرض التقديمي وتغيير ObservableCollection الحاجة ، ولكن هناك طريقة أكثر بساطة وأكثر أناقة لحل مجموعة المهام المشار إليها دون الحاجة إلى كتابة دراجة . لنبدأ الدراسة!

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


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



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



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

 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> العمل مع الأمر كما هو الحال مع حدث من النوع 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(); // : 42 

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

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

مقدمة في DynamicData


DynamicData هي مكتبة تتيح لك استخدام القوة الكاملة للملحقات التفاعلية عند العمل مع المجموعات. لا توفر الملحقات التفاعلية خارج الصندوق أفضل الطرق للعمل مع تغيير مجموعات البيانات ، وتتمثل مهمة 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


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

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

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

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


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

 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 أنواع .NET مضمنة لتعيين البيانات إلى العالم الخارجي. باستخدام مشغلات DynamicData القوية ، يمكننا تحويل IObservable<IChangeSet<Trade>> إلى ReadOnlyObservableCollection طراز العرض الخاص بنا.

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

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



مجموعات مترابطة واحدة وتغيير التتبع


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

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

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



استنتاج


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

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

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


All Articles