تطوير عبر الأنظمة الأساسية مع .NET ، البرمجة التفاعلية ، نمط MVVM وتوليد الكود

MVVM و .NET القياسي التفاعلي

تعد منصة .NET اليوم أداة عالمية حقًا - فبمساعدتها يمكنك حل مجموعة كبيرة من المهام ، بما في ذلك تطوير تطبيقات التطبيقات لأنظمة التشغيل الشائعة ، مثل Windows و Linux و MacOS و Android و iOS. في هذه المقالة ، سنلقي نظرة على بنية تطبيقات .NET عبر الأنظمة الأساسية باستخدام نمط تصميم MVVM والبرمجة التفاعلية . سوف نتعرف على مكتبات ReactiveUI و Fody ، ونتعلم كيفية تنفيذ واجهة INotifyPropertyChanged باستخدام السمات ، ولمس أساسيات AvaloniaUI ، و Xamarin Forms ، و Universal Windows Platform ، و Windows Presentation Foundation و .NET Standard ، وتعلم أدوات فعالة لطبقات نموذج اختبار الوحدة ونماذج عرض التطبيقات.

المادة عبارة عن تكيف لمقالتي " MVVM المتفاعلتين لمنصة .NET " و " تطبيقات .NET عبر الأنظمة الأساسية عبر نهج MVVM المتفاعل " ، التي نشرها المؤلف سابقًا على المورد المتوسط. نموذج التعليمات البرمجية متاح على GitHub .

مقدمة بنية MVVM و .NET عبر الأنظمة الأساسية


عند تطوير التطبيقات عبر الأنظمة الأساسية على النظام الأساسي .NET ، يجب عليك كتابة التعليمات البرمجية المحمولة والمعتمدة. إذا كنت تعمل مع أطر عمل تستخدم لهجات XAML ، مثل UWP و WPF و Xamarin Forms و AvaloniaUI ، يمكن تحقيق ذلك باستخدام نمط تصميم MVVM والبرمجة التفاعلية واستراتيجية فصل الرمز القياسي .NET. يعمل هذا النهج على تحسين إمكانية نقل التطبيقات من خلال السماح للمطورين باستخدام قاعدة تعليمات برمجية مشتركة ومكتبات برامج مشتركة على أنظمة تشغيل مختلفة.

سنلقي نظرة فاحصة على كل طبقة من طبقات تطبيق تم بناؤه على أساس بنية MVVM - النموذج وطريقة العرض و ViewModel. تمثل طبقة النموذج خدمات المجال وكائنات نقل البيانات وكيانات قواعد البيانات والمستودعات - كل منطق الأعمال لبرنامجنا. العرض مسؤول عن عرض عناصر واجهة المستخدم على الشاشة ويعتمد على نظام التشغيل المحدد ، ويسمح نموذج العرض بالتفاعل بين الطبقتين الموصوفتين أعلاه ، وتكييف طبقة النموذج للتفاعل مع المستخدم البشري.

توفر بنية MVVM تقسيم المسؤوليات بين طبقات البرامج الثلاث للتطبيق ، بحيث يمكن وضع هذه الطبقات في تجميعات منفصلة تهدف إلى .NET Standard. تسمح مواصفات .NET Standard الرسمية للمطورين بإنشاء مكتبات محمولة يمكن استخدامها في تطبيقات .NET المختلفة مع مجموعة واحدة موحدة من واجهات برمجة التطبيقات. بعد اتباع بنية MVVM وإستراتيجية فصل الرمز القياسي لـ .NET بدقة ، سنتمكن من استخدام طبقات النماذج الجاهزة ونماذج العروض التقديمية عند تطوير واجهة المستخدم لمختلف الأنظمة الأساسية وأنظمة التشغيل.

الصورة

إذا كتبنا تطبيقًا لنظام تشغيل Windows باستخدام Windows Presentation Foundation ، فيمكننا بسهولة نقله إلى أطر عمل أخرى ، على سبيل المثال ، Avalonia UI أو Xamarin Forms - وسيعمل تطبيقنا على أنظمة أساسية مثل iOS و Android و ستكون Linux و OSX وواجهة المستخدم هي الشيء الوحيد الذي يجب كتابته من الصفر.

تنفيذ MVVM التقليدي


تتضمن نماذج العروض التقديمية عادةً خصائص وأوامر يمكن ربط عناصر ترميز XAML بها. لكي تعمل ربط البيانات ، يجب على نموذج العرض تنفيذ واجهة INotifyPropertyChanged ونشر حدث PropertyChanged عندما تتغير أي خصائص لنموذج العرض. قد يبدو التنفيذ البسيط كما يلي:

public class ViewModel : INotifyPropertyChanged { public ViewModel() => Clear = new Command(() => Name = string.Empty); public ICommand Clear { get; } public string Greeting => $"Hello, {Name}!"; private string name = string.Empty; public string Name { get => name; set { if (name == value) return; name = value; OnPropertyChanged(nameof(Name)); OnPropertyChanged(nameof(Greeting)); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string name) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } } 

XAML يصف واجهة المستخدم للتطبيق:

 <StackPanel> <TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Text="{Binding Greeting, Mode=OneWay}"/> <Button Content="Clear" Command="{Binding Clear}"/> </StackPanel> 

ويعمل! عندما يدخل المستخدم اسمه في مربع النص ، يتغير النص أدناه على الفور ، تحية للمستخدم.

عينة ربط MVVM

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

وصفة # 1. قالب مراقب. الرسائل القصيرة والمستوطنون. واجهة المستخدم التفاعلية


في الواقع ، إن مشكلة التطبيق المطول والمربك لواجهة INotifyPropertyChanged ليست جديدة ، وهناك العديد من الحلول. أول شيء يجب الانتباه إليه ReactiveUI . هذا هو إطار عمل MVVM عبر الأنظمة الأساسية والوظيفية والتفاعلية يسمح لمطوري .NET باستخدام ملحقات تفاعلية عند تطوير نماذج العروض التقديمية.

الإضافات التفاعلية هي تطبيق لنمط تصميم المراقب المحدد بواسطة واجهات مكتبة .NET القياسية - IObserver و IObservable. تحتوي المكتبة أيضًا على أكثر من خمسين عامل تشغيل تسمح لك بتحويل تدفقات الأحداث - التصفية ، والدمج ، وتجميعها - باستخدام بناء جملة مشابه للغة الاستعلام المركبة LINQ . اقرأ المزيد عن تمديدات الطائرات هنا .

يوفر ReactiveUI أيضًا فئة أساسية تنفذ INotifyPropertyChanged - ReactiveObject. دعنا نعيد كتابة نموذج التعليمات البرمجية باستخدام الميزات التي يوفرها إطار العمل.

 public class ReactiveViewModel : ReactiveObject { public ReactiveViewModel() { Clear = ReactiveCommand.Create(() => Name = string.Empty); this.WhenAnyValue(x => x.Name) .Select(name => $"Hello, {name}!") .ToProperty(this, x => x.Greeting, out greeting); } public ReactiveCommand Clear { get; } private ObservableAsPropertyHelper<string> greeting; public string Greeting => greeting.Value; private string name = string.Empty; public string Name { get => name; set => this.RaiseAndSetIfChanged(ref name, value); } } 

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

وصفة # 2. التغليف INotifyPropertyChanged. خاصية رد الفعل


الحل البديل هو استخدام مكتبة ReactiveProperty ، التي توفر فئات مجمعة مسؤولة عن إرسال الإخطارات إلى واجهة المستخدم. باستخدام ReactiveProperty ، لا يتعين على نموذج العرض تنفيذ أي واجهات ؛ بدلاً من ذلك ، تطبق كل خاصية INotifyPropertyChanged نفسها. تقوم هذه الخصائص التفاعلية أيضًا بتنفيذ IObservable ، مما يعني أنه يمكننا الاشتراك في تغييراتها كما لو كنا نستخدم ReactiveUI . قم بتغيير نموذج العرض الخاص بنا باستخدام ReactiveProperty.

 public class ReactivePropertyViewModel { public ReadOnlyReactiveProperty<string> Greeting { get; } public ReactiveProperty<string> Name { get; } public ReactiveCommand Clear { get; } public ReactivePropertyViewModel() { Clear = new ReactiveCommand(); Name = new ReactiveProperty<string>(string.Empty); Clear.Subscribe(() => Name.Value = string.Empty); Greeting = Name .Select(name => $"Hello, {name}!") .ToReadOnlyReactiveProperty(); } } 

نحتاج فقط إلى الإعلان عن الخصائص التفاعلية وتهيئتها ووصف العلاقات بينها. لا حاجة لكتابة كود مرجعي بصرف النظر عن مُهيئ الخاصية. لكن هذا النهج له عيب - يجب علينا تغيير XAML لدينا حتى تعمل ربط البيانات. الخصائص التفاعلية هي أغلفة ، لذا يجب ربط واجهة المستخدم بالملكية الخاصة لكل غلاف من هذا القبيل!

 <StackPanel> <TextBox Text="{Binding Name.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Text="{Binding Greeting.Value, Mode=OneWay}"/> <Button Content="Clear" Command="{Binding Clear}"/> </StackPanel> 


وصفة # 3. تغيير التجميع في وقت الترجمة. PropertyChanged.Fody + ReactiveUI


في نموذج العرض التقديمي النموذجي ، يجب أن تكون كل خاصية عامة قادرة على إرسال إخطارات إلى واجهة المستخدم عندما تتغير قيمتها. مع PropertyChanged.Fody ، لا داعي للقلق حيال ذلك. الشيء الوحيد المطلوب من المطور هو وضع علامة على فئة نموذج العرض بسمة AddINotifyPropertyChangedInterface - وستتم إضافة التعليمات البرمجية المسؤولة عن نشر حدث PropertyChanged إلى المستوطنين تلقائيًا بعد إنشاء المشروع ، جنبًا إلى جنب مع تنفيذ واجهة INotifyPropertyChanged ، في حالة فقد أحدها. إذا لزم الأمر ، قم بتحويل خصائصنا إلى تدفقات من القيم المتغيرة ، يمكننا دائمًا استخدام طريقة ملحق WhenAnyValue من مكتبة ReactiveUI . دعونا نعيد كتابة نموذجنا للمرة الثالثة ، ونرى كيف سيكون نموذج العرض التقديمي أكثر إيجازًا!

 [AddINotifyPropertyChangedInterface] public class FodyReactiveViewModel { public ReactiveCommand Clear { get; } public string Greeting { get; private set; } public string Name { get; set; } = string.Empty; public FodyReactiveViewModel() { Clear = ReactiveCommand.Create(() => Name = string.Empty); this.WhenAnyValue(x => x.Name) .Select(name => $"Hello, {name}!") .Subscribe(x => Greeting = x); } } 

يغير فودي كود IL للمشروع في وقت الترجمة. تقوم الخاصية PropertyChanged.Fody بالبحث عن كافة الفئات التي تم تمييزها بسمة AddINotifyPropertyChangedInterface أو تنفيذ واجهة INotifyPropertyChanged وتحرير محددات هذه الفئات. يمكنك معرفة المزيد حول كيفية عمل إنشاء التعليمات البرمجية والمهام الأخرى التي يمكن حلها من تقرير أندريه كوروش " Reflection.Emit. Practice of Use ".

على الرغم من أن PropertyChanged.Fody يسمح لنا بكتابة تعليمات برمجية واضحة ومعبرة ، إلا أن الإصدارات القديمة من .NET Framework ، بما في ذلك 4.5.1 والإصدارات الأحدث ، لم تعد مدعومة. هذا يعني أنه في الواقع ، يمكنك محاولة استخدام ReactiveUI و Fody في مشروعك ، ولكن على مسؤوليتك الخاصة ومع مراعاة أن جميع الأخطاء التي تم العثور عليها لن يتم إصلاحها أبدًا! يتم دعم إصدارات .NET Core وفقًا لسياسة دعم Microsoft .

من النظرية إلى الممارسة. التحقق من النماذج مع ReactiveUI و PropertyChanged.Fody


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

 [AddINotifyPropertyChangedInterface] public sealed class FeedbackViewModel { public ReactiveCommand<Unit, Unit> Submit { get; } public bool HasErrors { get; private set; } public string Title { get; set; } = string.Empty; public int TitleLength => Title.Length; public int TitleLengthMax => 15; public string Message { get; set; } = string.Empty; public int MessageLength => Message.Length; public int MessageLengthMax => 30; public int Section { get; set; } public bool Issue { get; set; } public bool Idea { get; set; } public FeedbackViewModel(IService service) { this.WhenAnyValue(x => x.Idea) .Where(selected => selected) .Subscribe(x => Issue = false); this.WhenAnyValue(x => x.Issue) .Where(selected => selected) .Subscribe(x => Idea = false); var valid = this.WhenAnyValue( x => x.Title, x => x.Message, x => x.Issue, x => x.Idea, x => x.Section, (title, message, issue, idea, section) => !string.IsNullOrWhiteSpace(message) && !string.IsNullOrWhiteSpace(title) && (idea || issue) && section >= 0); valid.Subscribe(x => HasErrors = !x); Submit = ReactiveCommand.Create( () => service.Send(Title, Message), valid ); } } 

نقوم بتمييز نموذج العرض الخاص بنا بسمة AddINotifyPropertyChangedInterface - وبالتالي ، ستقوم جميع الخصائص بإعلام واجهة المستخدم بتغيير في قيمها. باستخدام طريقة WhenAnyValue ، سنشترك في التغييرات على هذه الخصائص وسنقوم بتحديث خصائص أخرى. سيظل الفريق المسؤول عن إرسال النموذج مغلقًا حتى يكمل المستخدم النموذج بشكل صحيح. سنحفظ التعليمات البرمجية الخاصة بنا في مكتبة الصف التي تستهدف .NET Standard وننتقل إلى الاختبار.

اختبار نموذج الوحدة


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

لإنشاء حشود وأرواح ، تعد مكتبة NSubstitute مفيدة لنا ، والتي توفر واجهة برمجة تطبيقات مناسبة لوصف ردود الفعل على إجراءات النظام والقيم التي يتم إرجاعها بواسطة "الكائنات المزيفة".

 var sumService = Substitute.For<ISumService>(); sumService.Sum(2, 2).Returns(4); 

لتحسين إمكانية قراءة كل من التعليمات البرمجية ورسائل الخطأ في اختباراتنا ، نستخدم مكتبة FluentAssertions . مع ذلك ، لن نضطر فقط إلى تذكر أي وسيطة في Assert ، حيث تحسب المكافأة القيمة الفعلية ، والقيمة المتوقعة ، ولكن IDE الخاص بنا سيكتب الرمز لنا!

 var fibs = fibService.GetFibs(10); fibs.Should().NotBeEmpty("because we've requested ten fibs"); fibs.First().Should().Be(1); 

لنكتب اختبارًا لنموذج العرض التقديمي الخاص بنا.

 [Fact] public void ShouldValidateFormAndSendFeedback() { //    , //    . var service = Substitute.For<IService>(); var feedback = new FeedbackViewModel(service); feedback.HasErrors.Should().BeTrue(); //   . feedback.Message = "Message!"; feedback.Title = "Title!"; feedback.Section = 0; feedback.Idea = true; feedback.HasErrors.Should().BeFalse(); //    , //   Send()  IService  //    . feedback.Submit.Execute().Subscribe(); service.Received(1).Send("Title!", "Message!"); } 


واجهة المستخدم لنظام التشغيل Windows العالمي


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

 <StackPanel Width="300" VerticalAlignment="Center"> <TextBlock Text="Feedback" Style="{StaticResource TitleTextBlockStyle}"/> <TextBox PlaceholderText="Title" MaxLength="{Binding TitleLengthMax}" Text="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Style="{StaticResource CaptionTextBlockStyle}"> <Run Text="{Binding TitleLength, Mode=OneWay}"/> <Run Text="letters used from"/> <Run Text="{Binding TitleLengthMax}"/> </TextBlock> <TextBox PlaceholderText="Message" MaxLength="{Binding MessageLengthMax}" Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Style="{StaticResource CaptionTextBlockStyle}"> <Run Text="{Binding MessageLength, Mode=OneWay}"/> <Run Text="letters used from"/> <Run Text="{Binding MessageLengthMax}"/> </TextBlock> <ComboBox SelectedIndex="{Binding Section, Mode=TwoWay}"> <ComboBoxItem Content="User Interface"/> <ComboBoxItem Content="Audio"/> <ComboBoxItem Content="Video"/> <ComboBoxItem Content="Voice"/> </ComboBox> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <CheckBox Grid.Column="0" Content="Idea" IsChecked="{Binding Idea, Mode=TwoWay}"/> <CheckBox Grid.Column="1" Content="Issue" IsChecked="{Binding Issue, Mode=TwoWay}"/> </Grid> <TextBlock Visibility="{Binding HasErrors}" Text="Please, fill in all the form fields." Foreground="{ThemeResource AccentBrush}"/> <Button Content="Send Feedback" Command="{Binding Submit}"/> </StackPanel> 

أخيرا ، شكلنا جاهز.

عينة uwp mvvm

واجهة المستخدم ل Xamarin.Forms


لكي يعمل التطبيق على الأجهزة المحمولة التي تعمل بأنظمة تشغيل Android و iOS ، تحتاج إلى إنشاء مشروع Xamarin جديد. أشكال الأشكال ووصف واجهة المستخدم باستخدام عناصر تحكم Xamarin المعدلة للأجهزة المحمولة.

xamarin.forms عينة mvvm

واجهة المستخدم لأفالونيا


Avalonia هو إطار عمل متعدد المنصات .NET يستخدم لهجة XAML المألوفة لمطوري WPF أو UWP أو Xamarin.Forms. يدعم Avalonia Windows و Linux و OSX ويتم تطويره بواسطة مجتمع من المتحمسين على GitHub . للعمل مع ReactiveUI ، يجب تثبيت حزمة Avalonia.ReactiveUI . وصف طبقة العرض التقديمي على Avalonia XAML!

عينة أفالونيا mvvm

الخلاصة


كما نرى ، يسمح لنا .NET في 2018 بكتابة برنامج متعدد الأنظمة بالفعل - باستخدام UWP و Xamarin.Forms و WPF و AvaloniaUI ، يمكننا تقديم الدعم لأنظمة تشغيل التطبيقات لدينا Android و iOS و Windows و Linux و OSX. يمكن لمكتبات وأنماط تصميم MVVM مثل ReactiveUI و Fody تبسيط وتسريع عملية التطوير من خلال كتابة تعليمات برمجية واضحة وقابلة للاستمرار ومحمولة. البنية التحتية المطورة والوثائق التفصيلية والدعم الجيد في برامج تحرير التعليمات البرمجية تجعل النظام الأساسي .NET أكثر جاذبية لمطوري البرامج.

إذا كنت تكتب تطبيقات سطح المكتب أو الجوال في .NET ولم تكن على دراية بـ ReactiveUI حتى الآن ، فتأكد من الانتباه إليها - يستخدم الإطار أحد عملاء GitHub الأكثر شيوعًا لنظام iOS ، وملحق Visual Studio لـ GitHub ، وعميل Atitian SourceTree git و Slack لنظام Windows 10 جوال يمكن أن تصبح سلسلة المقالات حول ReactiveUI على حبري نقطة انطلاق ممتازة. بالنسبة للمطورين على Xamarin ، من المحتمل أن تكون الدورة التدريبية " إنشاء تطبيق iOS باستخدام C # " من أحد مؤلفي ReactiveUI مفيدة. يمكنك معرفة المزيد حول تجربة التطوير على AvaloniaUI من المقالة حول Egram - عميل بديل لـ Telegram على .NET Core.

يمكن العثور على مصادر التطبيق عبر الأنظمة الأساسية الموصوفة في المقالة وتوضيح إمكانيات التحقق من النماذج مع ReactiveUI و Fody على GitHub . مثال على تطبيق عبر الأنظمة الأساسية يعمل على أنظمة التشغيل Windows و Linux و macOS و Android ، ويوضح استخدام ReactiveUI و ReactiveUI. يتوفر Fody و Akavache أيضًا على GitHub .

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


All Articles