پروژههای زیادی را میتوان یافت که اگر سورس کدهای آنها را بررسی کنیم، یک اسپاگتی کد تمام عیار را در آنها میتوان مشاهده نمود. منطق برنامه، قسمت دسترسی به دادهها، کار با رابط کاربر، غیره و غیره همگی درون کدهای یک یا چند فرم خلاصه شدهاند و آنچنان به هم گره خوردهاند که هر گونه تغییر یا اعمال درخواستهای جدید کاربران، سبب از کار افتادن قسمت دیگری از برنامه میشود.
همچنین از کدهای حاصل در یک پروژه، در پروژههای دیگر نیز نمیتوان استفاده کرد (به دلیل همین در هم تنیده بودن قسمتهای مختلف). حداقل نتیجه یک پروژه برای برنامه نویس، باید یک یا چند کلاس باشد که بتوان از آن به عنوان ابزار تسریع انجام پروژههای دیگر استفاده کرد. اما در یک اسپاگتی کد، باید مدتی طولانی را صرف کرد تا بتوان یک متد را از لابلای قسمتهای مرتبط و گره خورده با رابط کاربر استخراج و در پروژهای دیگر استفاده نمود. برای نمونه آیا میتوان این کدها را از یک برنامه ویندوزی استخراج کرد و آنها را در یک برنامه تحت وب استفاده نمود؟
یکی از الگوهایی که شیوهی صحیح این جدا سازی را ترویج میکند، الگوی MVP یا Model-View-Presenter میباشد. خلاصهی این الگو به صورت زیر است:
Model :
من میدانم که چگونه اشیاء برنامه را جهت حصول منطقی خاص، پردازش کنم.
من نمیدانم که چگونه باید اطلاعاتی را به شکلی بصری به کاربر ارائه داد یا چگونه باید به رخدادها یا اعمال صادر شده از طرف کاربر پاسخ داد.
View :
من میدانم که چگونه باید اطلاعاتی را به کاربر به شکلی بصری ارائه داد.
من میدانم که چگونه باید اعمالی مانند data binding و امثال آن را انجام داد.
من نمیدانم که چگونه باید منطق پردازشی موارد ذکر شده را فراهم آورم.
Presenter :
من میدانم که چگونه باید درخواستهای رسیده کاربر به View را دریافت کرده و آنها را به Model انتقال دهم.
من میدانم که چگونه باید اطلاعات را به Model ارسال کرده و سپس نتیجهی پردازش آنها را جهت نمایش در اختیار View قرار دهم.
من نمیدانم که چگونه باید اطلاعاتی را ترسیم کرد (مشکل View است نه من) و نمیدانم که چگونه باید پردازشی را بر روی اطلاعات انجام دهم. (مشکل Model است و اصلا ربطی به اینجانب ندارد!)
یک مثال ساده از پیاده سازی این روش
برنامهای وبی را بنویسید که پس از دریافت شعاع یک دایره از کاربر، مساحت آنرا محاسبه کرده و نمایش دهد.
یک تکست باکس در صفحه قرار خواهیم داد (txtRadius) و یک دکمه جهت دریافت درخواست کاربر برای نمایش نتیجه حاصل در یک برچسب به نام lblResult
الف) پیاده سازی به روش متداول (اسپاگتی کد)
protected void btnGetData_Click(object sender, EventArgs e)
{
lblResult.Text = (Math.PI * double.Parse(txtRadius.Text) * double.Parse(txtRadius.Text)).ToString();
}
اما این مشکلات را هم دارد:
- منطق برنامه (روش محاسبه مساحت دایره) با رابط کاربر گره خورده.
- کدهای برنامه در پروژهی دیگری قابل استفاده نیست. (شما متد یا کلاسی را اینجا با قابلیت استفاده مجدد میتوانید پیدا میکنید؟ آیا یکی از اهداف برنامه نویسی شیءگرا تولید کدهایی با قابلیت استفاده مجدد نبود؟)
- چگونه باید برای آن آزمون واحد نوشت؟
ب) بهبود کد و جدا سازی لایهها از یکدیگر
در روش MVP متداول است که به ازای هر یک از اجزاء ابتدا یک interface نوشته شود و سپس این اینترفیسها پیاده سازی گردد.
پیاده سازی منطق برنامه:
1- ایجاد Model :
یک فایل جدید را به نام CModel.cs به پروژه اضافه کرده و کد زیر را به آن خواهیم افزود:
using System;
namespace MVPTest
{
public interface ICircleModel
{
double GetArea(double radius);
}
public class CModel : ICircleModel
{
public double GetArea(double radius)
{
return Math.PI * radius * radius;
}
}
}
- خبری از textbox و برچسب و غیره نیست. اصلا نمیداند که رابط کاربری وجود دارد یا نه.
- خبری از رخدادهای برنامه و پاسخ دادن به آنها نیست.
- از این کد میتوان مستقیما و بدون هیچ تغییری در برنامههای دیگر هم استفاده کرد.
- اگر باگی در این قسمت وجود دارد، تنها این کلاس است که باید تغییر کند و بلافاصله کل برنامه از این بهبود حاصل شده میتواند بدون هیچگونه تغییری و یا به هم ریختگی استفاده کند.
- نوشتن آزمون واحد برای این کلاس که هیچگونه وابستگی به UI ندارد ساده است.
2- ایجاد View :
فایل دیگری را به نام CView.cs را به همراه اینترفیس زیر به پروژه اضافه میکنیم:
namespace MVPTest
{
public interface IView
{
string RadiusText { get; set; }
string ResultText { get; set; }
}
}
کار View دریافت ابتدایی مقادیر از کاربر توسط RadiusText و نمایش نهایی نتیجه توسط ResultText است البته با یک اما.
View نمیداند که چگونه باید این پردازش صورت گیرد. حتی نمیداند که چگونه باید این مقادیر را به Model جهت پردازش برساند یا چگونه آنها را دریافت کند (به همین جهت از اینترفیس برای تعریف آن استفاده شده).
3- ایجاد Presenter :
در ادامه فایل جدیدی را به نام CPresenter.cs با محتویات زیر به پروژه خواهیم افزود:
namespace MVPTest
{
public class CPresenter
{
IView _view;
public CPresenter(IView view)
{
_view = view;
}
public void CalculateCircleArea()
{
CModel model = new CModel();
_view.ResultText = model.GetArea(double.Parse(_view.RadiusText)).ToString();
}
}
}
کار این کلاس برقراری ارتباط با Model است.
میداند که چگونه اطلاعات را به Model ارسال کند (از طریق _view.RadiusText) و میداند که چگونه نتیجهی پردازش را در اختیار View قرار دهد. (با انتساب آن به _view.ResultText)
نمیداند که چگونه باید این پردازش صورت گیرد (کار مدل است نه او). نمیداند که نتیجهی نهایی را چگونه نمایش دهد (کار View است نه او).
روش معرفی View به این کلاس به constructor dependency injection معروف است.
اکنون کد وب فرم ما که در قسمت (الف) معرفی شده به صورت زیر تغییر میکند:
using System;
namespace MVPTest
{
public partial class _Default : System.Web.UI.Page, IView
{
protected void Page_Load(object sender, EventArgs e)
{
}
public string RadiusText
{
get { return txtRadius.Text; }
set { txtRadius.Text = value; }
}
public string ResultText
{
get { return lblResult.Text; }
set { lblResult.Text = value; }
}
protected void btnGetData_Click(object sender, EventArgs e)
{
CPresenter presenter = new CPresenter(this);
presenter.CalculateCircleArea();
}
}
}
در اینجا یک وهله از Presenter برای برقراری ارتباط با Model ایجاد میشود. همچنین کلاس وب فرم ما اینترفیس View را نیز پیاده سازی خواهد کرد.