PDA

توجه ! این یک نسخه آرشیو شده میباشد و در این حالت شما عکسی را مشاهده نمیکنید برای مشاهده کامل متن و عکسها بر روی لینک مقابل کلیک کنید : بررسی معماری mvc



Ali-Wizard
4 May 2010, 10:18 PM
بررسی معماری MVC - بخش اول

مقدمه نیاز روز افزون به کامپیوتر و مکانیزه کردن و سپردن تقریبی تمامی امور به دست ماشین امری است که انکار ناشدنی است . در این بین تولید کنندگان نرم افزار نیز تلاش میکنند تا نرم افزاری تولید کنند تا بتواند اکثر نیاز های متقاضیان را به بهترین نحو ممکن تامین کند ودر همین راستا در تلاش هستند که روند تولید نرم افزار را به سمتی بکشانند که ساختار استاندارد و تائید شده ای داشته باشد.
شاید اینطور بشه گفت که دوران کد نویسی به پایان رسیده و همه چیز به سمت زیر ساخت ها و بنیان نهادن چارچوب های استاندارد وپیروی از آن ها در امر تولید بهتر نرم افزار در حرکت است.
اجازه دهید ببینیم خصوصیات یک نرم افزار خوب چیست ؟
نامبردن تمامی خصوصیات یک نرم افزار خوب در این مقال نمی گنجد اما تعداد محدود و مهمی از آنها عبارتند از:


قابل حمل بودن
قابل استفاده مجدد بودن
قابل تغییر بودن
بهینه بودن از لحاط حافظه و زمان (زمان مهمتر از حافظه)
و......

مسئله ؟
بهتر است وجود مسئله را با یک مثال نشان دهم
فرض کنید نرم افزاری برای شرکتی نوشتید که یه بخش آن مقدار سود وزیان شرکت را در سال های مختلف بر اساس ارقام بیان میکند . حال صاحب برنامه پس از مدتی ازشما می خواهد برنامه را طوری تغییر دهید که همین اطلاعات را به گونه های مختلف دیگه ای مثلا نمودار های مختلف ( میله ای ، دایره ای و ...) در اختیار داشته باشد و یا حتی بخواهد انها را به فرمت خاصی و در فایل های خاصی ذخیره کند . در این مواقع چطور مشکل را حل میکنید؟

راه حل :
همانطور که گفته شد یکی از خصوصیات نرم افزار خوب قابل تغییر بودن آن میباشد. فرض کنید که برنامه را به این شکل طراحی کردید:
لینک ها فقط به اعضای سایت نمایش داده می شوند.
همانطور که در شکل نیز نشان داده شده است تمامی اعمال اععم از دریافت داده ها که مهمترین بخش است و همچنین پردازش آن ها همگی در یک فرم طراحی و پیاده سازی شده اند . و دقیقا مشکل همین جا نمایان میشود . ارتباط مستقیم با منبع داده بر قرار کردن جدا از اینکه مشکلات امنیتی دارد که بحث در مورد ان خارج از این مقال است ، باعث میشود که دست برنامه نویس را برای تغییرات آتی دربرنامه ببندد. چون داده ها درون خود فرم از منبع داده و به صورت مستقیم خوانده میشود پس دسترسی به داده های خواتده شده وجود ندارد . یا حداقل متحمل سربار زیادی می باشد.
راه کار های نوین:
یکی از راه کار هایی که امروزه بیشتر شاهد استفاده ان هستیم ، تولید نرم افزار بر اساس ساختار های لایه ای می باشد ، بدین صورت که کل نر افزار به تعداد لایه هایی تقسیم میشوند ، هر لایه وظیفه خاص خود را دارد و لایه ها از نتایج لایه های دیگر استفاده میکنند.
تعداد این لایه ها بسته به نرم افزار و طراحی میتواند 2 ، 3 ، 4 یا 5 لایه یا حتی بیشتر باشد . اما استاندارد ان که بیشتر از بقیه هم استفاده میشود 3 لایه هست و به روشی که بر اساس این تئوری پیاده سازی میشود اصطلاحا 3 Tire Programming گفته میشود.
در تئوری 3 لایه ، لایه ها عبارتند از


Data Access layer
Business Logic Layer
Presentation Layer

توضیحات بیشتر و نحوه پیاده سازی روش فوق را به عهده خود خواننده میگذارم .
پس از مقدمه ای مختصر وارد بحث اصلی یعنی MVC (Model View Controller ) می شویم .
تذکر : قبل از پرداختن به مثال عملی ابتدا توضیحاتی در مورد معماری MVC می دهم برای آن دسته از عزیزانی که با این معماری آشنا نیستند ، طبیعی است که افرادی که مباحث تئوری این معماری را میدانند مستقیما میتوانند به بخش بعدی که به زودی آماده می شود، بروند .
MVC چیست ؟

MvC مخفف سه کلمه Model View Controller هست . در واقع MVC بر روی معماری های چند لایه ای جهت جداسازی قسمت های مختلف برنامه و به طور دقیق تر جدا کردن بخش ها منطقی برنامه اععم از دیتا ، permission ها ، چک کردن صحت داده ها و .... از لایه Presentation layer یا در واقع همان لایه ای که مستقیما با کاربر نهایی (End user) در ارتباط است ،قرار میگیرد.
پس بر اساس توضیحات فوق میتوانیم هر یک از بخش های معماری MVC یعنی Model و View و controller را به شکل زیر تعریف کنیم.


Model

در واقع بار اصلی معماری MVC بر عهده این بخش است . این بخش میتواند با داده ها در ارتباط باشد .الزاما منظور از داده حتما ارتباط با پایگاه های داده همچون MSSQL و Access و ... نیست ، حتی منبع داده ها در بخش Model میتواند یک آرایه از اعداد و یا هر چیز دیگری باشد .
همچنین Model وظیفه چک کردن داده ها جهت صحت درستی داده ها را هم در بر عهده دارد (در این زمینه همکاری بیشتری با بخش Controller دارد) و همینطور وظایف دیگری که در مثال ها ی عملی که در آینده خواهم زد بیشتر آشنا خواهید شد.


View

این بخش که در واقع همان بخش Presentation Layer در معماری 3 لایه میباشد وظیفه بر قراری ارتباط با کاربر نهایی و گرفتن داده از کاربر و نمایش داده های اماده با کاربراز طریق برقراری ارتباط با دو بخش دیگر یعنی Model و controller است . در واقع نکته مهمی که در بخش View باید ان را مد نظر داشت این است که این لایه مسئول کنترل صحت داده های وارد شده از طریق کاربر و همچنین مسئول صحت داده های نشان داده شده به کاربر نیست . در واقع این بخش یا داده های خام کار میکند . به عنوان یک مثال ساده خیلی از برنامه نویسان موقعی که در فرم Login برنامه ،کاربر کلمه عبور خود را وارد میکند ، در همان فرم Login اقدام به چک کردن پسورد مبنی بر صحت آن و ... می کنند . که این عمل در معماری MVC قابل قبول نیست .
در واقع برای حل مسئله فوق در معماری MVC در فرم Login هنگامی که کاربر کلمه عبور را وارد کرد و دکمه Login یا ورود را زد ، کلمه عبور داده شده بدون هیچ گونه اعمالی اععم از Encrypt کردن و ... به بخش های دیگر فرستاده میشود و فقط یک نتیجه ساده مبنی بر این که کاربر اجازه ورود دارد یا خیر را از بخش های دیگر دریافت میکند
که بر اساس ان اجازه ورود کاربر به برنامه داده میشود .


Controller

این بخش همانطور که از اسم ان مشخص است یه بخش کنترل کننده می باشد ، و در واقع واسطی بین دو بخش Model و View میباشد.
حال ببینیم روند اجرای برنامه در معماری MVC به چه نحوی خواهد بود .
در معماری MVC روند کلی برنامه (جزئیات را در ادامه خواهید دید) به این شکل است که کاربر تقاضای خود را از طریق واسط های برنامه نویسی (نظیر Form ها و User Control ها و .. ) از برنامه (از بخش View)درخواست می کند . بخش View در خواست ها را به بخش Controller فرستاده و این بخش با برقراری ارتباط با بخش Model در خواست های کاربر را پردازش کرده و پس از پایان پردازش زمانی که خروجی درخواست داده شده آماده گردید بخش Controller بخش View را آگاه میسازد تا خود را بر اسا س تغییرات جدید که اصطلاحا در معماری MVC به آن حال Model می گویند ، به روز سازد . در واقع چیزی که باعث میشود تا بخش Controller به بخش View اطلاع دهد که باید حالت جدید model را دریافت کند و خود را Update کند این است که بخش View باید قبلا خودش را در بخش Model اصطلاحا Register کرده باشد که البته عمل Register کردن توسط بخش Controller انجاام میگیرد . نحوه register کردن بخش View به معماری آن محیط و همچنین زبانی که توسط آن برنامه را گسترش میدهید و همچنین قابلیت های آن زبان بستگی دارد.
زبان C# در این زمینه قابیلت ها ی زیادی دارد که میتوان معمماری MVC را بر اساس آنها پیاده سازی کرد . به عنوان نمونه برای برای Register کردن بخش View میتوان از یک متد ساده مثلا به نام RegisterMe که یک View را که معمولا فرم میباشد را register میکند یا از Interface ها که انعطاف پذیری بالایی دارند وبا عث قدرتمند تر کردن برنامه نویس جهت پیاده سازی اهداف میشوند و .... استفاده کرد .
همچنین از برای آگاه سازی بخش View جهت به روز شدن از روش های مختلفی از یک متد ساده تا استفاده کردن از Delegate ها و Event ها استفاده کرد.
قصد دارم که مثال های متنوعی در این باره بزنم که سعی خواهم کرد ابتدا از روش های ساده شروع کنم و کم کم به سمت روش های حرغه ای تر حرکت کنم که همگی بتوانند از این معماری در برنامه های خودشون استفاده کنند.
حال ببینیم توضیحاتی که در مورد روند کلی یک برنامه که بر اسا س معماری MVC نوشته میشود در قالب یک نمودار به چه شکلی خواهد بود. نمودار را قدم به قدم رسم میکنم و ابندا از ساده ترین نمودار شرو ع خواه کرد.
گفتیم که معماری MVC بر اساس سه بحش یا سه لایه Modelو View و Controller بنا میشود . و همه چیز تحت نظر این سه لایه باید انجام شود.

لینک ها فقط به اعضای سایت نمایش داده می شوند.
حال اقدام به برقراری ارتباط بین این سه بخش (لایه) بر اساس توضیحاتی که دادم میکنم.
خیلی از مقاله ها شروع معماری را از بخش کنترلر میدانند در واقع دست هم میگویند چون در نهایت بخش کنترلر است وظیفه handle کردن ابزار های مختلف مثل موس و .. را دارد. پس میتوان نمودار فوق را به شکل زیر کامل کرد:

لینک ها فقط به اعضای سایت نمایش داده می شوند.
اما من در نموادر فوق و بر اسا توضیحاتی که دادم فرض میکنم که کاربر درخواست هایش را از طریق فرم ها وارد کرده و منتظر نتیجه اطلاعات میباشد.

مراحل را به ترتیب عملیت و با شماره روی شکل نشان دادم .
لینک ها فقط به اعضای سایت نمایش داده می شوند.
توضیحات شکل بر اساس روند جریان برنامه:
مرحله 1) در این مرحله درخواست ها از سمت کاربر نهایی به ویو فرستاده میشود . پس جهت جریان اطالاعات در این مرحله از سمت End user به سمت View می باشد.
مرحله 2 ) در این مرحله کاربر درخواست خود را از طریق بخش View اعمال میکند .همانطور که گفته شد ویو مسئولیتی در قبال داده های وارد شده معمولا ندارد ،پس در خواست کاربر را به بخش کنترلر می فرستد. همچنین در همین مرحله ویو خودش را register می نماید تا بتداند اطلاعات جدید را دریافت نماید .جهت حرکت در قسمت 1 از سمت View به سمت Controller میباشد.
مرحله 3) پس از اینکه در خواست کاربر از View به Controller رسید و Controller بخش View را رجیستر کرد ، درخواست را به مدل داده و از مدل میخواهد که بر اساس درخواست کاربر به حالت جدید رفته و خود را به روز نماید. جهت جریان اطلاعات در این مرحله از سمت Controller به سمت Model میباشد.
مرحله 4) پس از اینکه مدل به درخواست ها رسیدگی کرد ، و به حالت جدید رفت این رفتار را به کنترلر پیغام میدهد . (همچنین اگر خطایی در این بین اتفاق بیفتد نیز کنترلر مطلع و به طبع آن در مرحله بعدی ویو نیز مطلع میگردد) .جهت جریان اطلاعات در این مرحله از سمت Model به سمت Controller میباشد.
مرحله 5) در این مرحله ، کنترلر موظف است که پیغامی به ویو بفرستد مبنی براینکه مدل تغییر کرده و به حالت جدید رفته است . این تغییر حالت الزاما دریافت داده های جدید از دیتا بیس و ... نیست بلکه هر نوع تغییری میتواند باشد ، مثلا یک Exception رخ داده است و ... جهت جریان اطلاعات در این مرحله از سمت Controller به سمت View می باشد.
مرحله 6) در این مرحله ویو موظف است اطلاعات جدید و حالت جدید مدل را از مدل تقا ضا کند . جهت جریان اطلاعات در این مرحله از سمت View به سمت Model می باشد.
مرحله 7) در این مرحله اطلاعات تغییر یافته و به طور کلی حالت جدید مدل (Model State) به ویو اطلاعا داده میشود. جهت جریان اطلاعات در این مرحله از سمت Model به سمت View میباشد.
مرحله 8) اطلاعات و تغییرات جدید سیستم که بر اساس در خواست مدل ایحاد گردیده به کاربر نهایی نشان داده میشود . باز هم الزامی نیست که حتما داده های جدید به کاربر نشان داده شوند ، این اطلاعات حتی میتواند یک پیغام مبنی بر اینکه یک Exception رخ داده است باشد. جهت جریان اطلاعات در این مرحله از سمت View به سمت End User است.
خلاصه بخش اول

همانطور که در شکل نشان داده شد ،درخواست های کاربر بایستی چندین مرحله را پشت سر بگذارند تا کاربر بتواند نتیجه درخواست های خود را ببیند. شاید در نگاه سطحی و نگاه اول شخصی از خودش بپرسد که چه نیازی است این همه کار اضافی انجام بگیرد تا به یک در خواست کاربر پاسخ داده شود در صورتی که میتوانستیم در همان فرمی که کاربر درخواست داده همه این کار ها را انجام دهیم؟ جواب این سوال را در بخش بعدی که به تدریج وارد مباحث عملی می شویم خواهید دید.
همچنین اینکه چگونه یک ویو باید خودش را REGISTER کند و اصلا register شدن در معماری MVCبه چه معنا می باشد ، همچنین نحوه اطلاع رسانی هر بخش به بخش دیگر و استفاده از نتایج بخش قبل را نیز در بخش های بعدی مفصلا شرح خواهم داد.

Ali-Wizard
4 May 2010, 10:26 PM
معماری MVC - بخش دوم

چکیده یکی از سوالات، در بین سوالات بسیاری که از من شده، این است که چگونه زمانی که دو فرم باز بر روی برنامه داریم، با تغییر داده ها در یک فرم، داده های تغییر یافته همزمان در فرم 2 مشخص شوند و فرم 2 نیز همزمان به روز رسانی شود. مثال این مقاله، علاوه بر نحوه پیاده سازی معماری MVC ، راه حل مناسب و درستی از مسئله فوق ارائه خواهد کرد.
در بخش اول مقاله MVC ، توضیحاتی در رابطه با معماری کلی این قالب، داده شد. در این بخش به بررسی یک مثال ساده از پیاده سازی این معماری خواهم پرداخت. در این مثال تنها منبع داده برنامه ، یک متغیر از نوع Double می باشد. این متغیر که در بخش مدل قرار گرفته است، توسط یکی از ویو ها که عمل Controller را برای ما انجام می دهد، قابل دسترسی و به روز رسانی می باشد. پس از تغییر حالت مدل به حالت جدید (تغییر داده به مقدار جدید)، ویو ها یا همان Observer ها ی رجیستر شده در بخش مدل با برقراری ارتباط با این بخش، درخواست تغییرات جدید را از بخش مدل کرده و بر اساس تغییرات جدید و ساختار داخلی خودش، خود را به روز رسانی می کند. هدف از این مثال، تنها آموزش مفاهیم این معماری به صورت عملی از جمله نحوه رجیستر کردن یک Observer در یک Observe، یا خارج کردن Observer از حالت رجیستر شده، نحوه آگاه شدن Observer ها از تغییرات مدل و دریافت تغییرات جدید و ... می باشد. بدیهی است که نمونه برنامه فرستاده شده همراه مقاله از لحاظ کد نویسی بهینه نمی باشد که این امر در پایان مقاله نیز با ذکر یک مثال نشان داده شده است. امید است که این مقاله در راستای ارتقای سطح کیفی برنامه نویسان مفید واقع شود.
مقدمه

در اين مثال ، منبع داده ها تنها يك عدد از نوع اعشاري مي باشد . همانطور كه قبلا نيز اشاره شد ، هيچ الزامي نيست كه منبع داده ، تعداد زياذي داده از يكي از جداول موجود در پايگاه داده SQL SERVER يا هر بانک داده ه ای ديگری باشد . گرچه معمولا اين چنين مي باشد . اما منبع داده مي تواند هر چيزي باشد .
اين مثال مي تواند شامل تعداد زيادي بخش ويو يا Observer باشد. Observer ها در مثال ما همان فرم هاي برنامه مي باشند . فرم اصلی برنامه مي تواند مقدار منبع داده يعني همان عدد اعشاري را تغيير دهد . بخش مدل علاوه بر تغيير دادن مقدار در عدد به مقدار جديد ، موظف است كه تغيير حالت مدل و سيستم را به Observer ها اطلاع دهد . Observer ها نيز موظف هستند كه با برقراري ارتباط با بخش مدل (Observe ) ، داده جديد را دريافت و خود را بر اساس آن به روز رساني كنند.
در اين مثال هر يك از Observer ها عمل جداگانه اي انجام مي دهند . به عنوان مثال يكي از Observer ها داده موجد را به توان 2 رسانيده و نشان مي دهد. Observer ديگر سينوس عدد را محاسبه كرده و نمايش مي دهد . در پايان برنامه متوجه خواهيد شد كه به راحتي مي توانيد Observer هاي ديگري به برنامه اضافه كرده كه هركدام عمل خاصي را بر اساس مقداد موجود در بخش مدل انجام دهند.
اگر چه اين مثال كاربرد عملي ندارد ولي سعي شده است كه اكثر جنبه هاي اين معماري پياده سازي شود . خواننده بايد پس از درك كامل اين مثال ،بتنواند اين معماري را به راحتي در برنامه هاي خود به كار گيرد . اين مثال كه كد كامل آن نيز همراه مقاله موجود می باشد ، از پنج فرم ، دو كلاس و دو اينترفيس استفاده مي كند كه در زير به توضيح آن ها خواهم پرداخت .
ابتدا به پيا ده سازي بخش هاي مدل و كنترلر مي پردازيم و سپس به توضح بخش ويو خواهيم پرداخت.
بخش مدل

بخش مدل شامل يك كلاس به نام MODEL و يك اينترفيس به نام IModel مي باشد كه كلاس Model از آن ارث بري مي كند و اعضاي آن را پياده سازي مي كند.
انترفيس IModel به صورت زير مي باشد.


using System;
using System.Collections.Generic;
using System.Text;
namespace FinalProject_MVC
{
public interface IModel
{
Double GetData
{
get;
}
}
}
همانطور كه در كد فوق مشخص است ، اينترفيس فوق داراي يك property به نام GetData مي باشد كه مقدارداده را در تغييرات مختلف مدل نگه داري مي كند . Observer ها از همين خاصيت استفاده كرده تا بتوانند داده جديد را دريافت كند.
كلاس مدل به شكل زير مي باشد


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace FinalProject_MVC
{
public class MODEL : IDisposable,IModel
{
#region declaration
List<frmBaseView> registerdObserverList = new List<frmBaseView>();

double Data = 0;
public event EventHandler ModelChanged;
#endregion

#region private Constructor
private MODEL()
{
}
#endregion

#region Singleton Instance
private static MODEL _singletonInstance;
public static MODEL SingletonInstance
{
get
{
if (_singletonInstance == null)
_singletonInstance = new MODEL();
return _singletonInstance;
}
}

#endregion

#region public members
public void AddObserver(frmBaseView observer)
{
if (!registerdObserverList.Contains(observer))
{
registerdObserverList.Add(observer);
this.Fire_ModelChanged(this, EventArgs.Empty);
}
else
throw new Exception("this observer was already registerd to model");
}
public void DeleteObserver(frmBaseView observer)
{
if (registerdObserverList.Contains(observer))
registerdObserverList.Remove(observer);
else
throw new Exception("this observer didn't register on model");
}

public void ChangeData(double newvalue)
{
this.Data = newvalue;
this.Fire_ModelChanged(this, EventArgs.Empty);
}
public void StartUpdateProccess()
{
foreach (frmBaseView observer in this.registerdObserverList)
{
observer.UpdateObserver(this);
}

}
#endregion

#region Fire Events
private void Fire_ModelChanged(object sender,EventArgs e)
{
if (ModelChanged == null)
return;
ModelChanged(sender, e);

}
#endregion

#region IDisposable Members
public void Dispose()
{
_singletonInstance = null;
}
#endregion

#region IModel Members
public double GetData
{
get
{
return this.Data;
}
}
#endregion
}
}
همانطور كه در كد فوق مشاهده مي كنيد ، كلاس MODEL به صورت Singleton Pattern پياده سازي شده است . اين به اين دليل است كه چون مي خواهيم كه ليستي از Observer ها را نگه داري كنيم ، بايستي اين ليست طوري باشد كه هنگام نمونه گيري از كلاس MODEL نمونه دگيري از اين ليست ساخته نشود تا در موقع Update كردن Observer ها با مشكل مواجه نشويم.
در بخش Declaration اين كلاس سه تعريف قرار گرفته است ، تعريف زير يك ليست ايجاد مي كند كه مي تواند ليستي از Observer ها رادر خود نگه داري كند .


List<frmBaseView> registerdObserverList = new List<frmBaseView>();
همانطور كه مشاهده مي كنيد در تعريف ليست فوق ، از مفاهيم چند ريختي استفاده شده است كه توضيح اين مفهوم از حوصله اين بحث خارج است .
تعريف دوم ، يك فيلد از نوع double تعريف شده كه به عنوان منبع داده نيز استفاده مي گردد.
در تعريف سوم ، يك Event تعريف شده است ، نكته اي كه در اين بابت بايد بدان توجه شود اين است كه هيچ Delegate اي در كلاس تعريف نشده است . وEvent تعريف شده بر اساس Delegateاي به نام Eventhandler پياده سازي شده است. Delegate ، EventHandler يكي از Delegate ها تعريف شده در خود دات نت مي باشد كه تعريفي مانند زير دارد


public delegate void EventHandler(object sender, EventArgs e);
در بخش اعضاي public اين كلاس ، چهار متد به شرح زير تعريف شده است.
متد AddObserver



public void AddObserver(frmBaseView observer)
{
if (!registerdObserverList.Contains(observer))
{
registerdObserverList.Add(observer);
this.Fire_ModelChanged(this, EventArgs.Empty);
}
else
throw new Exception("this observer was already registerd to model");
}
اين متد داراي آرگوماني از نوع FrmViewBase مي باشد. همانطور كه از نام اين متد مشخص است ، اين متد آرگومان ورودي خود چك كرده و چنانچه Observer قبلا وارد ليست Observer ها نشده باشد ، آن را را به ليست Observer هااضافه مي كند. چنانچه observer اي كه در آرگومان متد قرار دارد ، قبلا وارد ليست Observer ها شده باشد ، متد يك استثنا ايجاد كرده و پيامي مبني بر اينكه Observer مورد نظر قبلا در ليست اضافه شده است ،را صادر مي كند.
متد DeleteObserver



public void DeleteObserver(frmBaseView observer)
{
if (registerdObserverList.Contains(observer))
registerdObserverList.Remove(observer);
else
throw new Exception("this observer didn't register on model");
}
اين متد Observer اي كه در آرگومان خود قرار دارد را چنانچه در ليست Observer ها موجود باشد ، آن را حذف كرده و چنانچه Observer مورد نظر در ليست نباشد ، پيامي مبني بر وجود نداشتن Observer صادر مي گردد.
در دو متد فوق ، ورودي متدها از نوع frmBaseView مي باشد. همانطور كه در بخش هاي قبلي توضيح داده شد ، زماني كه به كلاسي از جنس پدر نياز باشد ، مي توانيم كلاسي از جنس فرزند را به آم بفرستيم . در توضيحاتي كه كمي بعد حواهد آمد خواهيد ديد كه كلاس frmBaseView ، به عنوان كلاس والد براي تمامي Observer ها عمل خواهد كرد.
متد ChangeData



public void ChangeData(double newvalue)
{
this.Data = newvalue;
this.Fire_ModelChanged(this, EventArgs.Empty);
}
اين متد آرگوماني از نوع double را دريافت مي كند ، و آن را به عنوان مقدار جديد عدد اعشاري يعني Data قرار مي دهد. پس از اين تغيير ، در خط دوم اين متد ، Event تعريف شده در اين كلاس يعني ModelChanged ، raise مي شود تا Observer ها را از تغييرات مدل آگاه سازد.
متد StartUpdateProccess



public void StartUpdateProccess()
{
foreach (frmBaseView observer in this.registerdObserverList)
{
observer.UpdateObserver(this);
}
}
اين متد توسط يك حلقه foreach ، متد UpdateObserver مربوط به Observer ها را فراخواني كرده و آرگوماني از جنس IModel به آن ارسال مي كند .
در بخش Fire Event از اين كلاس يك متد به نام Fire_ModelChange قرار گرفته است .


private void Fire_ModelChanged(object sender,EventArgs e)
{
if (ModelChanged == null)
return;
ModelChanged(sender, e);
}
در خط اول اين كد ابتدا چك مي شود كه اگر رويداد ModelChange برابر با null باشد ، كنترل برنامه از متد خارج شود (توسط دستور return) و اگر رويداد modelChange مخالف با null بود ، آن را فراخواني مي كند و آرگومان هاي مربوطه كه يكي از جنس object و ديگري از جنس EventArgs مي باشد را به رويداد مي فرستد.
در بخش IDisposable Members ، يك متد به نام Dispose تعريف شده است . اين متد جزء اعضاي اينترفيسي به نام IDisposable مي باشد ، كه كلاس MODEL از آن ارث بري كرده و ملزم به پياده سازي اعضاي آن مي باشد. در اين متد موقعي كه نمونه گرفته شده از كلاس MODEL در حال ِDispose شدن است ، مقدار _singletonInstancr ، برابر با null مي شود تا در نمونه گيري هاي بعدي برنامه با ايراد مواجه نشود. و در آخرين بخش از تعاريف اين كلاس يعني IModel Members يك property به نام GetData مي باشد،كه مقدار فعلي Data را بر مي گرداند.
بخش كنترلر

بخش كنترلر شامل يك كلاس مس ياشد كه تعريف آن در زير آمده است:


using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace FinalProject_MVC
{
class CONTROLLER
{
#region Declarations
MODEL _model;

#endregion
#region Constructor
public CONTROLLER()
{
//
//init _Model
//
_model = MODEL.SingletonInstance;
}
#endregion

#region public Members
public void RegisterObserver(frmBaseView observer)
{
_model.ModelChanged += new EventHandler(OnModel_ModelChanged);
if (_model == null)
return;
_model.AddObserver(observer);
}
public void UnRegisterObserver(frmBaseView observer)
{
if (_model == null)
return;
_model.DeleteObserver(observer);
}

public void ChangeData(double newData)
{
_model.ModelChanged += new EventHandler(OnModel_ModelChanged);
if (_model == null)
return;
_model.ChangeData(newData);
}
void OnModel_ModelChanged(object sender, EventArgs e)
{
if (_model == null)
return;
_model.StartUpdateProccess();
}
#endregion
}
}
در بخش Declaration مربوط به اين كلاس ، اعلاني صورت گرفته كه يه نمونه از كلاس Model تعريف شده كه در بخش Constructor كلاس مقدار دهي مي شود ودر واقع حافظه مربوطه به آن اختصاص داده مي شود.
اين كلاس داراي سه متد public و يك متد private مي باشد كه در ادامه توضيحات مربوط به هريك را خواهيد ديد
متد RegisterObserver



public void RegisterObserver(frmBaseView observer)
{
if (_model == null)
return;
_model.ModelChanged += new EventHandler(OnModel_ModelChanged);
_model.AddObserver(observer);
}
اين متد يك Observer به عنوان آرگومان دريافت كرده و آن را به متد AddObserver از كلاس MODEL مي فرستد تا در ليست Observer ها قرار گيرد.
همچنين در ابتداي اين متد با استفاده از رويداد ModelChanged و عملگر Cast ، متدي به نام OnModel_ModelChange را مشخص مي كند كه در هنگام raise شدن رويداد ModelChanged بايستي فراخواني شود.
متد UnRegisterObserver



public void UnRegisterObserver(frmBaseView observer)
{
if (_model == null)
return;
_model.DeleteObserver(observer);
}
اين متد Observer ي كه در آرگومان خود دارد را به متد DeleteObserver از كلاس MODEL مي فرستد تا از ليست Observer ها حذف گردد. در دو متد فوق دستور زير را مي بينيد


if (_model == null)
return;
اين دستور ابتدا چك مي كند كه اگر model برابر با null است ، از ادامه اجراي دستورات جلوگيري شود و كنترل برنامه از متد خارج شود.
متد ChangeData



public void ChangeData(double newData)
{
if (_model == null)
return;
_model.ModelChanged += new EventHandler(OnModel_ModelChanged);
_model.ChangeData(newData);
}
اين متد مقدار جديد داده را به متد changeData از كلاس MODEL مي فرستد ، تا مقدار جديد ، جايگزين مقدار قبلي شود. همچنين كد هاي مربوط به raise كردن رويداد ModelChange در اين متد پياده سازي شده است.
متد OnModel_ModelChanged



void OnModel_ModelChanged(object sender, EventArgs e)
{
if (_model == null)
return;
_model.StartUpdateProccess();
}
اين متد در زماني كه رويداد ModelChange ، raise مي شود ، فراخواني مي شود تا متد startUpdateProccess را از كلاس MODEL فراخواني كند ، تا Observer ها تغييرات جديد مدل را دريافت كرده و خود را به روز رساني كنند.
بخش ويو

در اين مثال فرمي داريم به نام frmBaseView ، همانطور كه حدس زده ايد ، اين فرم يك فرم پدر يا فرم والد مي باشد . در واقع چون ظاهر Observer ها مانند يكديگر مي باشد ، بهتر است كه يك فرم به عنوان فرم پدر ايجاد كنيم و بقيه Observer ها از اين فرم ارث بري كنند. طرح فرم frmBaseView به صورت زير مي باشد.

لینک ها فقط به اعضای سایت نمایش داده می شوند.
شکل 1 – 1 فرم frmBaseView
بر روي فرم فوق دو عدد Label ، دو عدد textbox و دو عدد Button قرار گرفته است . Textbox اي كه روبري main Data قرار گرفته است ، هميشه مقدار فعلي داده موجود در بخش مدل را نگه داري مي كند . TextBox اي كه روبروي related Data قرار گرفته است بسته به عملي كه Observer انجام مي دهد ، تغيير مي كند . به عنوان مثال اگر عدد موجود در بخش مدل يعني main Data برابر با 30 باشد و Observer عمل Sinus گرفتن از داده را انجام دهد ، آن گاه TextBox دوم مقدار 0.5 كه برابر با سينوس 30 درجه مي باشد را نشان خواهد داد.
اين فرم از اينترفيسي به نام IVIEW ارث بري مي كند .اينترفيس IVIEW به صورت زير مي باشد.


using System;
using System.Collections.Generic;
using System.Text;
namespace FinalProject_MVC
{
public interface IView
{
void UpdateObserver(IModel model);
}
}
همانطور كه مشاهده مي كنيد ، اينترفيس فوق داراي متدي به نام UpdateObserver مي باشد. اين متد يك آرگومان از جنس IModel در يافت مي كند . فرم frmBaseView ، چون از اينترفيس ارث بري مي كند ،ملزم به پياده سازي متد updateObserver مي باشد. كد هايي كه در فرم frmBaseView نوشته شده را در زير مشاهده خواهيد كرد.


public partial class frmBaseView : Form ,IView
{
public frmBaseView()
{
InitializeComponent();
}
#region IView Members

public void UpdateObserver(IModel model)
{
if (model == null)
return;
double newData = model.GetData;
this.ContinueUpdateProccess(newData);
}
#endregion

private void btnUnRegister_Click(object sender, EventArgs e)
{
Button senderButton = sender as Button;

btnUnRegister.Enabled = false;
btnRegister.Enabled = true;
CONTROLLER MVC_Controller = new CONTROLLER();
MVC_Controller.UnRegisterObserver((frmBaseView)sen derButton.FindForm());

}

private void btnRegister_Click(object sender, EventArgs e)
{
Button senderButton = sender as Button;
btnRegister.Enabled = false;
btnUnRegister.Enabled = true;
CONTROLLER MVC_Controller = new CONTROLLER();
MVC_Controller.RegisterObserver((frmBaseView)sende rButton.FindForm());
}

protected virtual void ContinueUpdateProccess(double NewData)
{

}

private void frmBaseView_Load(object sender, EventArgs e)
{
CONTROLLER MVC_Controller = new CONTROLLER();
MVC_Controller.RegisterObserver((frmBaseView)sende r);

}

private void frmBaseView_FormClosing(object sender, FormClosingEventArgs e)
{
//
//Un register Observer
//
CONTROLLER MVC_Controller = new CONTROLLER();
MVC_Controller.UnRegisterObserver((frmBaseView)sen der);
}
}
}
همانطور كه مشاهده مي كنيد ، كلاس frmBaseView از كلاس Form ارث بري مي كند (چون يك فرم است) و همچنين اين كلاس از انترفيس IView نيز ارث بري مي كند.
متد UpdateObserver



public void UpdateObserver(IModel model)
{
if (model == null)
return;
double newData = model.GetData;
this.ContinueUpdateProccess(newData);
}
اين متد كه در واقع عضو انترفيس IView مي باشد ، جهت Update كردن Observer به كار مي رود. اين متد داراي يك آرگومان از جنس IModel مي باشد ، اين متد مقدار داده را از طريق خاصيت GetData از كلاس MODEL دريافت مي كند و آن را جهت Update كردن Observer مربوطه به متد ContinueUpdateproccess مي فرستد.
متد ContinueUpdateProccess



protected virtual void ContinueUpdateProccess(double NewData)
{
}
همانطور كه مشاهده مي كنيد بدنه اين متد خالي مي باشد. در واقع اين متد به صورت Protect و Virtual تعريف شده استو. اين بدان جهت است كه Observer هايي كه از frmBaseView ارث بري مي كنند ، اين متد را Override كنند و بر اساس مقداري كه در آرگومان اي متد است ، خود را به روز رساني كنند. اين عمل باعث مي شود كه به طور اتوماتيك آن متد مربوط به Observer ي فراخواني شود كه در بخش مدل آن را notify كرده است.
رويداد كليكك مربوط به btnRegister



private void btnRegister_Click(object sender, EventArgs e)
{
Button senderButton = sender as Button;
btnRegister.Enabled = false;
btnUnRegister.Enabled = true;
CONTROLLER MVC_Controller = new CONTROLLER();
MVC_Controller.RegisterObserver((frmBaseView)sende rButton.FindForm());
}
كد فوق مربوط به رويداد كليلك مربوط به btnRegister مي باشد كه در فرم با عنوان RegisterMe مشخص شده است. در اين متد (رويداد ها نيز متد هستند) ابتدا يك عمل Cast در خط اول صورات مي گيرد ، اين خط از كد باعث مي شود كه تشخيص بدهيم ، كليد registerme از كدام Observer كليك شده است. در ادامه دستورات با فراخواني متد RegisterObserver از كلاس CONTROLLER ، Observer مورد تظر را رجيستر مي كنيم. نحوه تشخيص Observer توسط دستور زير مشخص مي شود .


(frmBaseView)senderButton.FindForm()
اين دستور ابتدا از متد FindForm ، Observer ي كه دكمه كليك شده در آن قرار دارد ، تشخيص داده مي شود و سپس توسط يك تبديل cast ، Observer تشخيص داده شده به نوعي frmBaseView تبديل مي شود تا بتواند در Observe رجيستر شود.
رويداد كليكك مربوط به btnUnRegister


private void btnUnRegister_Click(object sender, EventArgs e)
{
Button senderButton = sender as Button;

btnUnRegister.Enabled = false;
btnRegister.Enabled = true;
CONTROLLER MVC_Controller = new CONTROLLER();
MVC_Controller.UnRegisterObserver((frmBaseView)sen derButton.FindForm());
}

كد فوق مربوط به رويداد كليلك مربوط به btnUnRegister مي باشد كه در فرم با عنوان UnRegisterMe مشخص شده است. اين كد دقيقا عملي مشابه با كليد btnRegisterMe انجام مي دهد ، با اين تفاوت كه Observer تشخيص داده شده از ليست Observer ها حذف مي شود.
رويداد Load مربوط به فرم



private void frmBaseView_Load(object sender, EventArgs e)
{
CONTROLLER MVC_Controller = new CONTROLLER();
MVC_Controller.RegisterObserver((frmBaseView)sende r);
}
كد فوق مربوط به رويداد load ، از فرم frmBaseView مي باشد. اين كد باعث مي شود هنگامي كه Observwer ي نمونه سازي و نمايش داده مي شود توسط اين كد تشخيص داده شده و در ليست Observer ها رجيستر شود.
رويداد Form_Closing مربوط به فرم



private void frmBaseView_FormClosing(object sender, FormClosingEventArgs e)
{
CONTROLLER MVC_Controller = new CONTROLLER();
MVC_Controller.UnRegisterObserver((frmBaseView)sen der);
}
رويداد فوق ، هر زمان كه يكي از Observer ها در حال بسته شدن باشد ، اتفاق مي افتد و باعث مي شود كه آن Observer از ليست Observer ها حذف گردد. در اين مثال سه Observer به نام هاي frmSinusView ، frmCostineview و frmExponentialView طراحي شده اند كه هرسه از كلاس frmBaseView ارث بري مي كنند و متد ContinueUpdateProccess را بر اساس عملي كه انجام مي دهند ، پياده سازي مي كنند. در زير اشكال هر يك از سه Observer را همراه با كد هاي مربوط به متد continueUpdateProccess را مشاهده خواهيد كرد.
frmSinusView و متد ContinueUpdateProccess


لینک ها فقط به اعضای سایت نمایش داده می شوند.
شکل 1 – 2 فرم frmSinusView


protected override void ContinueUpdateProccess(double NewData)
{
this.txtMainData.Text = NewData.ToString();
this.txtVariableData.Text = Math.Sin((Math.PI*NewData)/180).ToString();
}
frmCostineView و متد ContinueUpdateproccess


لینک ها فقط به اعضای سایت نمایش داده می شوند.
شکل 1 – 3 فرم frmCosineView


protected override void ContinueUpdateProccess(double NewData)
{
this.txtMainData.Text = NewData.ToString();
this.txtVariableData.Text = Math.Cos((Math.PI * NewData) / 180).ToString();
}
frmExponentialView و متد ContinueUpdateProccess


لینک ها فقط به اعضای سایت نمایش داده می شوند.
شکل 1 – 4 فرم frmExponentialView


protected override void ContinueUpdateProccess(double NewData)
{
this.txtMainData.Text = NewData.ToString();
this.txtVariableData.Text = (NewData * NewData).ToString();
}
آخرين قسمتي كه ار مثال فوق تشريح خواهيم كرد ، فرم اصلي برنامه مي باشد كه با نام frmMain تعريف شده است. اين فرم در شكل زير نشان داده شده است.

لینک ها فقط به اعضای سایت نمایش داده می شوند.
شکل 1 – 5 فرم اصلي برنامه مربوط به مثال 4-1
خروجي برنامه پس از كليك كردن بر روي كليد هاي Sinus ، Cosine و Exponential

لینک ها فقط به اعضای سایت نمایش داده می شوند.
شکل 1 – 7 خروجي برنامه پس از كليك كردن بر روي كليد هاي Sinus ، Cosine و Exponential
خروجي برنامه پس از وارد كردن عدد 30 در فرم MVC Example و فشردن كليد Update

لینک ها فقط به اعضای سایت نمایش داده می شوند.
شکل 1 – 8 خروجي برنامه پس از وارد كردن عدد 30 در فرم MVC Example و فشردن كليد Update
خروجي برنامه پس از تغييرعدد 30 به 20و فشردن كليد UnRegisterme از فرم ExponentialView (پايين شكل بالا در سمت راست) و سپس فشردن كليد Update

لینک ها فقط به اعضای سایت نمایش داده می شوند.
شکل 1 – 9 خروجي برنامه پس از تغييرعدد 30 به 20و فشردن كليد UnRegisterme از فرم ExponentialView (پايين شكل بالا در سمت راست) و سپس فشردن كليد Update
همانطور كه در شكل فوق مشخص است ، فرم ExponentialView كه در پايين و سمت راست شكل فوق قرار دارد، از ليست Observer ها حذف شده و با تغيير در داده ، تغييري در حالت آن به وجود نيامده است.
در پايان اين مثال مجددا متذكر مي شوم كه اين هدف از اين مثال ، تشريح و روش پياده سازي معماري MVC در برنامه ها مي باشد. مي توان كد هاي اين برنامه را توسعه داد و همچنين آن ها را بهينه كرد. به عنوان مثال مي توان كد مربوط به متد ChangeData از كلاس MODEL را به صورت زير تغيير داد


public void ChangeData(double newvalue)
{
if (this.Data == newvalue)
return;
this.Data = newvalue;
this.Fire_ModelChanged(this, EventArgs.Empty);
}
تغيير كد به صورت فوق باعث مي شود كه چنانچه مقدار جديد با مقدار فعلي برابر است از Update كردن Observer ها جلوگيري شود.
خلاصه

در بخش دوم معماری MVC به بیان نحوه پیاده سازی این معماری در زبان برنامه نویسی سی شارپ پرداخته شد. انتظار می رود که خواننده گرامی پس از درک کامل مطالی دو مقاله و بررسی مثال زده شده در این بخش، درک نسبتا کاملی از معماری MVC پیدا کند و بتواند این معماری را در برنامه های بزرگ و تجاری به کار ببرد. در پایان هرگونه سوال، پیشنهاد و یا انتقادی از مطالب این بخش دارید، را با من در میان گذارید تا نسبت به آن اقدامات لازم صورت گیرد.


منبع: جامعه برنامه نویس