في الأسبوع الماضي ، كنت أقرأ بعض المقالات حول استخدام WCF ، إطار الكيان وكيفية نقل الكيانات عبر حدود الخدمة. تضمنت إحدى المقالات التي واجهتها مشروعًا تجريبيًا استفاد من نمط عرض النموذج (MVP).
بعد استكشاف المشروع التجريبي ، اعتقدت أنه قد يكون من المثير للاهتمام كتابة مقال حول هذا النمط. سوف يزودك محرك البحث المفضل لديك بسعادة مع عدد كبير من الروابط إلى مقالات أخرى تشرح هذا النمط تمامًا.
بالنسبة لهذه المقالة ، قررت توفير تطبيق ملموس والتركيز بشكل أقل على النظرية الكامنة وراء هذا النمط.
دعنا نلف ...
بالطبع مطلوب القليل من النظرية ، لذلك دعونا نخرجه من الطريق.
كما يمكنك أن تخصم من اسم نمط MVP يتكون من ثلاثة أجزاء مميزة: النموذج والعرض والمقدم. يلعب كل من هذه الأجزاء دوره الخاص في إنشاء فصل بين المخاوف بين طبقة العرض والوصول إلى البيانات والبيانات.
النموذج مسؤول عن التعامل مع الوصول إلى البيانات ، يتواصل مقدم العرض مع النموذج ويمرر البيانات من ذلك. يستقبل العرض بيانات من مقدم العرض ويمرر البيانات مرة أخرى ، فهو لا يتواصل مباشرة مع النموذج. مقدم العرض هو الانتقال بين العرض والنموذج.
الشكل 1 - تفاعل نمط MVP

تصور الصورة أعلاه العرض على أنه تطبيق واجهة. يجب أن تكون طبقة العرض التقديمي هي تطبيقات ASP.NET أو WinForms أو WPF لتنفيذ واجهات عرض واحدة أو أكثر. يتواصل مقدم العرض بدوره مع تطبيق العرض من خلال هذه الواجهة ، فهو لا يعرف شيئًا عن التنفيذ الفعلي نفسه.
يوفر هذا اقتران فضفاض ويمنع الكود الخاص بك من الاعتماد على التكنولوجيا المستخدمة في طبقة العرض التقديمي. والفكرة هي أنه يجب أن تكون قادرًا على استبدال هذه الطبقة دون أن تؤثر على منطقك وبينك للوصول إلى البيانات.
قد يبدو كل هذا غامضًا بعض الشيء ، ولكن يجب أن تنطلق الأمور بمجرد الانتقال إلى النقاط التالية التي توفر تنفيذًا فعليًا لهذا النمط. على سبيل المثال ، سيتم استخدام تطبيق ASP.NET.
المكان الأكثر منطقية للبدء به هو النموذج. نظرًا لأنها مسؤولة عن التعامل مع الوصول إلى البيانات وتخزينها ، نحتاج أولاً إلى إنشاء متجر للبيانات الفعلية. لهذا استخدمت SQL Server 2005 Express ، وأنشأت قاعدة بيانات جديدة تسمى SouthWind وأضفت جدولًا واحدًا بعنوان العميل. يحتوي الجدول على 3 حقول ، وهي:
هذا كل شيء لقاعدة البيانات. دعنا نطلق النار على Visual Studio 2008 وننشئ حلًا فارغًا جديدًا يدعى Avppattern. بعد ذلك ، أضف مكتبة فئة بعنوان "قاعدة البيانات".
عادةً ما أعطي المشروع اسمًا يتبع نمط "Company.Product.library" ، ولكن من أجل البساطة ، لنبقيه قصيرًا وبسيطًا. قم أيضًا بحذف ملف class1.cs المُنصّر بعد إضافة المشروع إلى الحل.
دعنا ننشئ نموذجًا من قاعدة البيانات باستخدام إطار الكيان (EF). لذا تأكد من أنك تستخدم Visual Studio 2008 ولديك حزمة الخدمة 1 مثبتة في Visual Studio و .NET Framework 3.5. يمكنك تنزيل حزم الخدمة هنا.
أضف نموذج بيانات إطار الكيان إلى مكتبة الفصل عن طريق تحديد إضافة عنصر جديد ، نموذج بيانات ado.net من قائمة سياق المشروع في مستكشف الحل. سيعرض Visual Studio الآن معالج نموذج بيانات الكيان. قم بتسمية النموذج Southwind ودع Visual Studio إنشاء النموذج لك. عندما سئل عن كائنات قاعدة البيانات التي تريد تضمينها في النموذج الخاص بك ، فقط حدد جدول العميل من عقدة الجداول.
ملاحظة : إذا لم تكن على دراية بإنشاء نماذج بيانات مع إطار الكيان ، أقترح بشدة هذا الفيديو التدريبي من Alex James. يوضح لك كيفية إنشاء نموذج بيانات كيان بسيط من نقطة الصفر.
يوضح الشكل 2 نموذج بيانات الكيان الناتج. لا يمكن أن يصبح النموذج أبسط بكثير من هذا. يتم ذلك عن قصد للحفاظ على الأمور بسيطة قدر الإمكان والحفاظ على التركيز على نمط MVP.
الشكل 2 - نموذج بيانات الكيان

تأكد من إعادة تسمية أنواع الكيانات والكيانات إلى اسم مناسب بعد إنشاء نموذج البيانات. تتمثل قاعدة الإبهام في استخدام اسم واحد لـ ContityType و Pressing for EntitySets. لذلك في هذه الحالة ، يجب تسمية ContityType العميل وعملاء EntitySet. إن اسم ERITITYTYPE على ما يرام بالفعل لأنه يرثها من جدول العميل ، لذلك فقط حدد عميل ENTITYTYTYPE وضبط خاصية اسم كيانها.
الشكل 3 - اسم مجموعة الكيان

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

الآن وبعد أن أصبح النموذج في مكانه ، دعنا نضيف طبقة عمل فوقه حيث يمكننا تحديد منطق أعمالنا المخصص. من أجل البساطة ، سأبقي كمية الكود محدودة في هذه الطبقة.
لاحظ أن هذه الطبقات تفرض فصلًا منطقيًا فقط (N-Layer) ، وليس المادية (N-tier). توجد جميع الطبقات على نفس الجهاز ، على الرغم من أنه يمكنك بالتأكيد اختيار شطبها عبر آلات / مستويات متعددة وإنشاء تطبيق موزع أو مستطيل حقًا.
لإعداد طبقة العمل ، أضف مكتبة فئة جديدة إلى الحل ودعوة الأعمال. قم بإعادة تسمية ملف class.cs الافتراضي إلى customermanager.cs. أضف أيضًا إشارات إلى قاعدة بيانات مكتبة الفئة التي تم إنشاؤها مسبقًا وتجميع system.data.entity.
تعرض قائمة 1 فئة CustomerManager التي تحتوي على بعض منطق العمل للعمل مع كيان العميل من نموذج بيانات الكيان (EDM). الكود إلى حد كبير محسوسة ذاتيا.
قائمة 1 - فئة Customermanager
using System . Collections . Generic ;
using System . Linq ;
using Database ;
namespace Business
{
public class CustomerManager
{
private readonly SouthwindEntities context ;
#region Constructor(s)
public CustomerManager ( )
{
context = new SouthwindEntities ( ) ;
}
#endregion
#region Methods
// Retrieve a generic list of Customer entities.
// This method will return all the customers found in the Customer table.
public List < Customer > GetCustomers ( )
{
var q = from c in context . Customers
select c ;
return q . ToList ( ) ;
}
#endregion
}
}الشكل 5 - الحل المحدث

ملاحظة : سيتضمن إعداد تطبيق N-tier تقديم طبقة خدمة تدعوها طبقة العرض التقديمي. تستخدم طبقة الخدمة بعد ذلك كائنات العمل الموجودة في طبقة العمل. لم يعد هناك ارتباط مباشر بين العرض التقديمي وطبقة العمل ، تعمل طبقة الخدمة كوسيط. في مقال مستقبلي ، سوف أتناول هذا من خلال إظهار كيفية نقل كيانات EF عبر حدود الخدمة.
تحتوي طبقة العمل فقط على طريقة واحدة مفيدة ، وهي "قائمة getCustomers ()". سوف يستدعي مقدم العرض في نمط MVP هذه الطريقة على كائن أعمال CustomerManager من أجل توصيل البيانات إلى العرض.
يعرض تطبيق المثال فقط قائمة العملاء باستخدام نمط MVP. قد يكون هذا مبالغة قليلاً ، ولكن يتم الاحتفاظ به بهذه البساطة حسب التصميم. الهدف الرئيسي لهذا "Hello World!" نوع التطبيق هو الحصول على الفكرة عبر كيفية تنفيذ هذا النمط. الوظيفة الفعلية التي يقدمها التطبيق ليست مهمة.
سيحتاج تطبيق العرض الفعلي (صفحة ASPX ، WinForms ، WPF ... إلخ) إلى تطبيق واجهة عرض. يحتاج تطبيق العرض إلى إنشاء مثيل للمقدم وتمرير نفسه كمعلمة في مُنشئه. يحتوي مُنشئ مقدم العرض على معلمة واحدة وهي نوع واجهة العرض.
يسرد قائمة 2 واجهة IVIEW التي سننفذها قريبًا في صفحة ASP.NET ASPX. لديها حدث واحد يسمى إعداد VERPHING. يستخدم حدث PreparView مندوبًا يحدد توقيعه أنه لا يعيد شيئًا ولا يأخذ أي معلمات.
يجب أن يرفع العرض هذا النوع من "الأحداث الفارغة" فقط للدلالة على مقدم العرض بأنه يجب تنفيذ بعض الإجراءات. يشير الإجراء في هذه الحالة إلى أنه يجب على مقدم العرض تحديث قائمة العملاء التي يحتفظ بها تطبيق العرض. يمكن للمقدم الوصول إلى قائمة العملاء هذه من خلال خاصية عملاء Ilist والتي يتم إعلانها كجزء من الواجهة.
قائمة 2 - عرض واجهة
public delegate void VoidEventHandler ( ) ;
public interface IView
{
event VoidEventHandler PrepareView ;
IList < Customer > Customers { set ; }
}لإضافة واجهة العرض إلى مشروعك أولاً ، أضف مشروع مكتبة فئة جديد إلى الحل الذي يسمى العرض التقديمي. بعد ذلك ، أضف واجهة جديدة ونسخ والصق الرمز الموضح في القائمة أعلاه. ستحتوي مكتبة رمز العرض التقديمي أيضًا على مقدمي العروض والواجهات التي ينفذونها.
أقوم بفصل واجهات العرض والمقدمين في مكتبة رمز منفصلة بحيث يمكنك بسهولة مشاركتها بين "أطراف العرض" المتعددة مثل ASP.NET و WinForms و WPF ... وما إلى ذلك. يوضح الشكل 6 كيف اخترت تنظيم مكتبة الرمز هذه.
الشكل 6 - الحل المحدث

لا تنس إضافة إشارات إلى قاعدة البيانات ومشاريع الأعمال وتجميع System.Data.Entity.
بالنسبة للجزء الأخير من نمط MVP ، نحتاج إلى تقديم مقدم. يأخذ فئة Customerprespresenter المعروضة في القائمة 3 إشارة إلى تطبيق IVIEW في مُنشئها. وبهذه الطريقة ، يمكن أن تتواصل مع العرض دون معرفة أي شيء عن التنفيذ الفعلي. هذا الاقتران فضفاض هو الذي يجعل نمط MVP مناسبًا جدًا لـ "أطراف العرض" المختلفة.
أيضًا في المُنشئ ، يتم توصيل جميع أحداث واجهة العرض بمعالج الأحداث. في هذه الحالة هناك حدث واحد فقط. تم توصيل حدث PrepareView بمعالج حدث View_prepareview. وهذا بدوره يستدعي الطريقة الخاصة للمقدم GetCustomers () التي تُرجع مجموعة العملاء "المنعشة" وتعيينها لمجموعة العملاء التي يحتفظ بها تطبيق العرض.
قائمة 3 - فئة CustomerPresenter
public class CustomersPresenter : ICustomersPresenter
{
#region Fields
private readonly IView view ;
#endregion
#region Constructor(s)
public CustomersPresenter ( IView view )
{
// Save a reference to the view
this . view = view ;
// Hook up an event handler for the events of the view
view . PrepareView += view_PrepareView ;
}
#endregion
#region Private methods
private List < Customer > GetCustomers ( )
{
return new CustomerManager ( ) . GetCustomers ( ) ;
}
#endregion
#region ICustomersPresenter Members
public virtual void view_PrepareView ( )
{
view . Customers = GetCustomers ( ) ;
}
#endregion
}مقدم العرض أعلاه ينفذ أيضا واجهة. في رمز مصدر العينة ، يتم ترك هذه الواجهة فارغة. لقد وضعت هناك فقط لأغراض توضيحية. يمكنك تجسيد هذه الواجهة إذا كنت ترغب في ذلك. قد تحتاجه لإطار اختبار الوحدة المفضل لديك لسخرية مقدمي العروض على سبيل المثال.
لذا فإن صفحة العرض أو ASPX في حالتنا تحتاج فقط إلى تنفيذ واجهة العرض وإعداد حدث PreparView من أجل استلام قائمة العملاء المحدثة من مقدم العرض. لا تتواصل العرض نفسه مع قاعدة البيانات أو طبقة العمل مباشرة. يعالج مقدم العرض التواصل مع طبقة العمل التي تسترجع البيانات عن طريق معالجة النموذج (أو طبقة الوصول إلى البيانات إذا صح التعبير).
لإنهاء هذا المقال ، دعونا نرى كيف يجتمع كل هذا في مشروع تجريبي ASP.NET. أضف مشروعًا جديدًا باستخدام قالب مشروع تطبيق ASP.NET Web إلى الحل. أضف إشارات إلى مشاريع العرض التقديمي وقواعد البيانات وتجميع System.Data.Entity.
أضف GridView يسمى "GridView1" وزر يسمى "Btnrefresh" إلى صفحة Default.aspx. أضف الرمز في القائمة 4 إلى الكود خلف الصفحة.
القائمة 4 - رمز default.aspx وراء
public partial class _Default : System . Web . UI . Page , IView
{
private CustomersPresenter presenter ;
protected override void OnInit ( EventArgs e )
{
presenter = new CustomersPresenter ( this ) ;
}
protected void Page_Load ( object sender , EventArgs e )
{
if ( ! IsPostBack )
{
PrepareView ( ) ;
}
}
protected void btnRefresh_Click ( object sender , EventArgs e )
{
PrepareView ( ) ;
}
#region IView Members
public event VoidEventHandler PrepareView ;
public IList < Database . Customer > Customers
{
set
{
GridView1 . DataSource = value ;
GridView1 . DataBind ( ) ;
}
}
#endregion
}جميع التعليمات البرمجية الموجودة وراء صفحة ASPX هي تنفيذ واجهة iview ، وإنشاء مقدم العرض وتمرير تطبيق iview ، كونه نفسه ، في مُنشئه. ثم يبقى كل ما يبقى هو تشغيل حدث إعداد IVIEW في الأوقات المناسبة.
أثناء إنشاء مقدم العرض ، يتم تعيين معالج الأحداث تلقائيًا لهذا الحدث والذي يتأكد من أنه عندما يتم تشغيله ، يعرف مقدم العرض كيفية تحديث مجموعة العملاء التي يتم الاحتفاظ بها بواسطة الصفحة. الصفحة نفسها لا تعرف شيئًا عن من أين تأتي هذه البيانات أو كيف يتم استردادها. أخرق منظر كان ذلك أفضل.
عند عرض هذه الصفحة في متصفح ، هذه هي النتيجة:
الشكل 7 - عرض موقع ASP.NET

ملاحظة : لا تنس إضافة ConnectionString المطلوبة بواسطة إطار الكيان إلى ملف تكوين Web.Config. يمكنك العثور على ConnectionString في ملف App.Config لمشروع مكتبة فئة قاعدة البيانات. تم إدخاله هناك تلقائيًا عندما قام Visual Studio بإنشاء نموذج بيانات الكيان.
بالطبع ، لن تعمل سلاسل الاتصال في رمز المصدر العينة على جهاز الكمبيوتر الخاص بك حيث تم بناؤها ضد قاعدة بيانات محلية لي. لذا تأكد من ضبطها وفقًا لذلك.
الشكل 8 - الحل المحدث

نظرًا لأن الخطوة الأخيرة من هذه المقالة ، دعنا ننشئ طريقة عرض باستخدام تطبيق Windows Forms فقط لإظهار مدى مرونة نمط MVP حقًا. الخطوات اللازمة للقيام بذلك متطابقة تقريبًا مع المثال السابق لإنشاء موقع ويب ASP.NET. أضف تطبيق Windows Forms الجديد إلى الحل الخاص بك وإضافة إشارات إلى مشروع قاعدة البيانات والعرض التقديمي وتجميع System.Data.Entity.
بعد ذلك ، أضف DataGridView والتحكم في الزر إلى النموذج. يتم عرض رمز النموذج في القائمة أدناه. إنها متطابقة تقريبًا مع صفحة Default.aspx. لا تنس أيضًا إضافة سلسلة اتصال Entity Framework إلى ملف تكوين App.Config.
قائمة 5 - رمز Form1.CS
using System ;
using System . Windows . Forms ;
using Presentation . Presenters ;
using Presentation . ViewInterfaces ;
namespace WindowsFormsApplication
{
public partial class Form1 : Form , IView
{
private CustomersPresenter presenter ;
public Form1 ( )
{
InitializeComponent ( ) ;
presenter = new CustomersPresenter ( this ) ;
}
private void Form1_Load ( object sender , EventArgs e )
{
PrepareView ( ) ;
}
private void btnRefresh_Click ( object sender , EventArgs e )
{
PrepareView ( ) ;
}
#region IView Members
public event VoidEventHandler PrepareView ;
public System . Collections . Generic . IList < Database . Customer > Customers
{
set
{
dataGridView1 . DataSource = value ;
}
}
#endregion
}
}Voila ، لقد انتهينا أخيرًا. لاحظ أنه على الرغم من أن مثال ASP.NET و Winforms متطابقان تقريبًا في الكود ، فقد لا يكون هذا هو الحال في التطبيقات الأكثر تعقيدًا. تختلف أنواع التفاعلات في هذين النوعين من واجهات المستخدمين اختلافًا كبيرًا ، وقد لا تكون هذه المقدمة في جميع المواقف واضحة.
الشكل 9 - توضيح تطبيقات Windows Forms

الشكل 10 - الحل المحدث

بالنسبة لهذه المقالة ، تم استخدام التفسير الكلاسيكي لنمط MVP وأظهره من خلال تنفيذه حل ASP.NET. يعتبر نمط MVP الأصلي "متقاعد" منذ أن أعلن مارتن فاولر ذلك. يمكن تقسيم النمط إلى معسكرين الآن ، كونه:
اقرأ مقالة أنماط وممارسات Microsoft لمزيد من المعلومات.
كتبت هذا المقال لتزويد تنفيذ ملموس لنمط MVP حيث كنت أستكشفه في ذلك الوقت. ومع ذلك ، قد يكون من الحكمة البقاء بعيدًا عن ذلك والانتظار حتى تصدر Microsoft إطار ASP.NET MVC. بالنسبة لأولئك الذين يرغبون في تقليد إطار عمل-عرض النماذج (MVC) ، أقترح الآن قراءة هذه المقالة بواسطة أنماط وممارسات Microsoft.
ملاحظة : نمط MVP هو مشتق لنمط وحدة التحكم في عرض النموذج (MVC). في وقت كتابة هذا التقرير ، كانت Microsoft مشغولة حاليًا بتطوير إطار عمل ASP.NET MVC. عند تعيين الهندسة المعمارية لمشروع موقع ويب جديد ، أقترح التحقق من هذا الإطار.
يمكن تحديد الفرق الرئيسي بين أنماط MVP و MVC لمن المسؤول عن التعامل مع إدخال المستخدم مثل أحداث لوحة المفاتيح و Moue. في نمط MVP ، فإن واجهة المستخدم الرسومية نفسها مسؤولة ويحتاج إلى تفويضها إلى مقدم العرض من خلال الأحداث. في نمط MVC ، تكون وحدة التحكم مسؤولة عن التعامل مع هذه الأحداث.