۱۳۹۱ بهمن ۱, یکشنبه

استفاده از الگوی Proxy در MVVM - قسمت اول


نکته: برای درک موضوع زیر لازمه اندکی با MVVM و مفاهیم Binding در WPF/SilverLight آشنا باشید.

شاید برای خیلی از شماها که سعی دارید از MVVM در برنامه های WPF/SilverLight استفاده کنید پیش اومده باشه که چطوری می تونم یک Model رو از طریق ViewModel کنترل و مقدار دهی کنم؟ برای این کار میشه از الگوی طراحی Proxy استفاده کرد. در تعریف Proxy Design Pattern اومده:


Proxy به طور کلی الگویی است که در آن یک کلاس (شیء) امکانات کلاس (شیء) دیگری را ارائه می دهد. این کلاس (شیء) می تواند واسط هر چیزی باشد، از قبیل یک Connection در شبکه، یک شیء بزرگ در حافظه، یک فایل یا هر منبعی که کپی گرفتن از آن پرهزینه یا غیرممکن باشد.

خب برای نمونه Model بسیار ساده زیر رو در نظر بگیرید:
  
  
  public class Person
  {
      public int Id { get;set; }
      public string FullName { get;set; }
  }

کد بالا یک کلاس معمولی CLR (معروف به POCO مخفف Plain Old CLR Object) رو تعریف می کنه که میشه ازش در ORM مورد نظرمون (مثلا EntityFramework، NHibernate و ...) استفاده کرد. حالا مسئله ای که پیش میاد اینه که هنگام bind کردن این کلاس به View در WPF/SilverLight با تغییر اطلاعات درون View، اطلاعات مربوط به Model به روز نمیشن. میشه واسط INotifyPropertyChanged رو برای این کلاس پیاده سازی کرد، ولی باعث میشه کلاس از حالت POCO خارج بشه. هرچند این به خودی خود باعث هیچ مشکلی نیست ولی بهتره برای ساده نگه داشتن کلاس های Model از این کار خودداری بشه. همینطور ممکنه بخوایم یک کنترل منطقی رو هنگام تغییر مقدار بعضی خصوصیات انجام بدیم که خوب نیست کدش توی Model نوشته بشه. بطور کلی توصیه میشه Model همیشه ساده و مستقل از تکنولوژی (خواه WPF/Silverlight، یا Windows Forms یا حتی ASP.NET) باشه. اکثرا عملیات کنترلی به ViewModel ها سپرده میشن. مثلا برای اعتبار سنجی یا مقداردهی های خاص به ازای سایر مقادیر و غیره. اصلا از کجا معلوم همه اطلاعاتی که قراره در یک View نمایش داده بشه در Model مورد نظر وجود داشته باشه؟ با این که ViewModel ها بطور مستقیم هیچ ارجاعی به View ندارند اما عملیات و داده های فراهم شده شون همیشه متناسب با View هاست. مثلا اگه شما در View یک DataGrid یا ListBox دارید، حتما توی ViewModel اون، یک مشخصه مثلا از نوع ListCollectionView یا ObservableCollection خواهید داشت. همینطور اگه دکمه ای روی View قرار داره به احتمال زیاد یک ICommand یا پیاده سازی مرتبط با اون رو توی ViewModel شاهد هستیم. حرف های تئوری کافیه! کد نویسی رو ادامه بدیم.

برای طراحی یک ViewModel مناسب ابتدا یک کلاس واسط می نویسیم:
  public abstract class ViewModel<T> : INotifyPropertyChanged
  {
      public virtual T Model { get; set; }
 
      public ViewModel(T model)
      {
          if(model == null)
              throw new ArgumentNullException("model");
          this.Model = model;
      }
 
      public event PropertyChangedEventHandler PropertyChanged;
      protected virtual void OnPropertyChanged(string propertyName)
      {
          var handler = PropertyChanged;
          if (handler != null)
              handler(this, new PropertyChangedEventArgs(propertyName));
      }
  }
نکته: اگه از کتابخانه Prism استفاده می کنید توصیه می کنم بجای وراثت از INotifyPropertyChanged از NotificationObject استفاده کنید. اگر اسم Prism به گوشتون نخورده، بعد از خوندن این پست حتما یه نگاهی بهش بندازید.

سپس ViewModel مورد نظر رو پیاده می کنیم. دقت کنید از اونجایی که کلاس زیر رو برای برنامه های WPF/Silverlight طراحی کردیم، واسط INotifyPropertyChanged رو برای کلاس پدر پیاده کردیم. بنابراین در برنامه های ASP.NET MVC نیازی به این پیاده سازی ندارید. همینطور شما می تونید بنا به نیازتون پیاده سازی فیلدهای مربوط به کلید اصلی (مثلا Id) رو در ViewModel انجام ندید.
  public class PersonViewModel : ViewModel<Person>
  {
      public PersonViewModel(Person model) : base(model)
      {
      }
      public PersonViewModel() : this(new Person())
      {
      }
  
      public int Id
      {
          get
          {
              return Model.Id;
          }
          set
          {
              if (Model.Id != value)
              {
                  Model.Id = value;
                  OnPropertyChanged("Id");
              }
          }
      }
  
      public string FullName
      {
          get
          {
              return Model.FullName;
          }
          set
          {
              if (Model.FullName != value)
              {
                  Model.FullName = value;
                  OnPropertyChanged("FullName");
              }
          }
      }
  }

خب. تا اینجا یک ViewModel ساده داریم که نقش Proxy رو برای Model ما (که همون کلاس Person باشه) بازی می کنه. از PersonViewModel میشه برای bind کردن به یک View استفاده کرد. از اونجایی که ViewModel ها همیشه اینقدر ساده نیستند، پس این پست هنوز تموم نشده و ادامه شو در پست بعدی توضیح می دم.