معماری MVVM در WPF

معماری MVVM یا ( Model-View-View Model ) یک الگوی معماری است که در مهندسی نرم افزار و تکنولوژی های برنامه نویسی شرکت مایکروسافت استفاده می شود و به عنوان الگوی طراحی مدل، توسط مارتین فاولر معرفی شده است.

معماری MVVM به صورت هدفمند و برای توسعه پلتفرم UI ها حرفه ای که از برنامه نویسی رویداد محور پشتیبانی می کنند به وجود آمده است از جمله برنامه هایی که از برنامه نویسی رویداد محور استفاده می کنند می توان به: WPF, Silver Light و ZK Framework اشاره کرد.

معماری MVVM امکانات بی نظیری را برای جدا سازی لایه Graphic User Interface و لایه Business Logic و یا کد های منطقی پشت مدل در اختیار شما قرار می دهد همچنین به عنوان یک Data Model انعطاف پذیر از View Model شناخته می شود.


دلایل استفاده از MVVM:

  • همکاری با دیگر برنامه نویسان

با جدا کردن قسمت UI برنامه از کد مربوطه، این امکان فراهم می شود تا متخصصین هر قسمت بتوانند در زمان یکسان بر روی پروژه کار کنند، یعنی زمانی که طراحان مشغول کار با UI هستند، توسعه دهندگان هم مشغول توسعه کد برنامه هستند بدون آنکه نیاز باشد هر دو با هم بر روی فایل های یکسان کار کنند.

  • سهولت در تست برنامه

معماری MVVM رابطه بین منطق برنامه و UI را از بین می برد و باعث می شود تا توانایی تست برنامه بالاتر برود.

با استفاده از MVVM هر قسمت از کد ریز تر می شود و اگر درست اجرا بشود، وابستگی های داخلی و خارجی داخل قسمت های جدایی از کد که شامل منطق برنامه است (قسمت های مورد نیاز برای تست)، قرار می گیرند.

  • آسان تر کردن نگهداری پروژه

با داشتن یک جدا کننده بین قسمت های مختلف برنامه یک ساختار و یکنواختی به کد های برنامه می دهد و باعث می شود تا پیدا کردن مکان های مختلف آسان شود.


ارتباط بین اجزای MVVM
ارتباط بین اجزای MVVM


سه قسمت اصلی MVVM در WPF:


ویو - View

این بخش شمال تمامی عناصر UI پروژه میشود به عبارت دیگری تمام آن چیزی که کاربر از برنامه مشاهده میکند دراین بخش قرار می گیرد، در زبان View، XAMLمی تواند به صورت Window, User Controls و یا Resource Dictionaries باشد.


ویو مدل - View Model

این قسمت شامل Object هایی هست که داده ها و تابع ها را برای هریک از View ها فراهم می کنند. در کل یک رابطه یک به یک بین کلاس های View و View Model وجود دارد. کلاس های View Model داده ها را برای View ارسال می کند و Command ها را برای مدیریت UI فراهم می کند. برخلاف روش های دیگر، View Model نباید اطلاعاتی در مورد View مربوط به خودش داشته باشد. این قسمت یک اصل کیلیدی روش MVVM است.

درواقع View Model رابطه بین View و Model است.


مدل - Model

به طور کلی Model دسترسی به داده ها (Data) و سرویس هایی که برنامه نیاز دارد را فراهم میکند. زمانی که View Model درحال کنار هم قرار دادن اطالاعات Model هست، Class های موجود در Model کار اصلی برنامه را انجام می دهند. اگر شما در حال استفاده از Dependency injection باشید، Class های موجود در Model به صورت پارامتر های سازنده interface به View model شما ارسال می شوند.


از آنجایی که تعامل بین View model و Model به برنامه شما بستگی دارد، برای ادامه این مقاله فقط روی رابطه بین Model و View Model تمرکز می کنیم.




اتصال - Binding

توانایی Bind کردن، استفاده از MVVM را فراهم میکند. Bind ها در View تعریف می شوند و Properties های داخل View را به Properties های View Model متصل می کند.

برای مثال:

C#

public class ViewModel 
{
  public string FirstName { get; set; }
}

XAML

<TextBlock Text=&quot{Binding Path=FirstName}&quot VerticalAlignment=&quotCenter&quot HorizontalAlignment=&quotCenter&quot/>

کد بالا متعلق به قسمت شروعی از الگوی MVVM است، Binding مقدار موجود در Text Block، آن را برابر با Property FirstName موجود در کلاس View Model قرار می دهد. اگر این کد اجرا بشود مقدار Text Block خالی خواهد بود چونکه هیچ چیزی برای متصل کردن کلاس View Model به Window وجود ندارد، در WPF می تواند با استفاده از Data Context این ارتباط را ایجاد کرد.

مقدار Data Context را می توان در کلاس سازنده Window تنظیم کرد، اگر هیچ Data Context ای به عنصری از UI متصل نباشد، Data Context را از کلاس پدر خود به ارث می برد. بنابرین، تنظیم کردن Data Context در داخل Window ، آن را برای تمامی عناصر داخل آن تنظیم می کند.

public MainWindow()
{
var viewModel = new ViewModel();
viewModel.FirstName = &quotArman&quot
DataContext = viewModel;
InitializeComponent();
}

حال با اجرا کردن برنامه، Text Block مقدار “Arman” را نشان خواهد داد.

دقت کنید که بر اساس روش MVVM تنها کدی که باید داخل MainWindow.cs و یا دیگر View ها وارد شود DataContext است، در این جا ما برای ساده کردن مقاله این کد را در اینجا وارد کردیم، روش اصلی بعد از معرفی Command ها بیان شده است!

یکی از بهترین نکات در مورد Bind ها این است که UI را با داده های داخل View Model هماهنگ می کند. برای مثال اگر بخواهیم مقدار Text block قبلی را عوض کنیم:

public MainWindow()
{
var viewModel = new ViewModel();
viewModel.FirstName = &quotArman&quot
DataContext = viewModel;
InitializeComponent();
viewModel.FirstName = &quotMehdi&quot
}

اگر برنامه را اجرا کنیم Text Block همچنان مقدار “Arman” را نمایش خواهد داد، با این وجود که مقدار Property FirstName تقییر کرده اما هیچ اطلاع رسانی به Binding برای تغییر دادن مقدارش وجود ندارد. این مشکل را می تواند با پیاده سازی (INPC (INotifyPropertyChanged برطرف کرد. این Interface یک Event مخصوص دارد که به Binding اطلاع می دهد که property خاصی تغییر کرده و هر bind ای که از آن استفاده می کند باید مقدارش را به روزرسانی کند.


از INPC می توان به شکل زیر استفاده کرد:

public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string FirstName { get; set; }
public void d(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}


public MainWindow()
{
var viewModel = new ViewModel();
viewModel.FirstName = &quotArman&quot
DataContext = viewModel;
InitializeComponent();
viewModel.FirstName = &quotMehdi&quot
viewModel.d(nameof(ViewModel.FirstName));
}

حال Text Block مقدار “Mehdi” را نشان خواهد داد.

با این حال، استفاده از این ایونت برای هر بار که مقدار Porperty تغییر می کند می تواند بسیار خسته کننده شود. اما از آنجا که استفاده از MVVM بسیار فرا گیر شده است، FrameWork های زیادی هم برای فراهم کردن یک کلاس پایه برای کلاس View model ساخته شده اند.


دستورها - Commands

استفاده از Bind ها یک راه عالی برای انتقال داده ها از View Model به View به شمار می رود، اما ما همچنان نیاز داریم که به View Model اجازه بدهیم تا بتواند به UI پاسخ بدهد. اکثر User Control ها که یک مقدار پیش فرض UI دارند (مانند یک دکمه) توسط Command ها مدیریت می شوند. تمامی User Control هایی که ICommandSource را پیاده سازی می کنند، از یک Command property پشتیبانی می کنند، که زمانی که عمل پیش فرض یک کنترل رخ می دهد، فراخوانی می شود. تعداد بسیار زیادی از کنترل ها این Interface را پیاده سازی می کنند، مانند: دکمه ها، منو ها و چک باکس ها.

دستور ها Object هایی هستند که ICommand را اجرا می کنند و یا به بیانی دیگر، Command ها پیام هایی هستند که از View به View Model فرستاده می شوند. زمانی که Event پیش فرض کنترلی (مانند کلیک کردن دکمه) رخ می دهد، Method داخل Commnad فراخوانی می شود. بنابراین Command ها هم می توانند وقتی قابل فراخوانی شدن باشند، نشان داده شوند. این موضوع به کنترل اجاره می دهد تا خودش را بر اساس فعال شدن یا نشدن Command، فعال یا غیرفعال بکند.


مثال برای دستور ها:

همانطور که در بالا دیده شد، ما میتوانیم مقدار FistName را هنگامی که روی دکمه ای کلیک شد، تغییر دهیم.

در ابتدا باید یک Command به View Model اضافه کنیم:

public class ViewModel : ViewModelBase
{
 public ICommand ChangeNameCommand { get; }
     ...
}

سپس، یک دکمه به Main Window اضافه می کنیم و با استفاده از Binding مقدار Command موجود در این صفحه را به Command داخل View Model متصل می کنیم:

XAML

<Button Content = &quotChange Name&quot Command=&quot{Binding Path=ChangeNameCommand}&quot />

حالا فقط نیاز داریم که یک Command Object جدید به ChangeNameCommand property موجود در View Model اختصاص دهیم. متاسفانه، WPF شامل یک ICommand پیش فرض مناسب برای استفاده در View Model نیست، با این وجود پیاده سازی Interface بسیار سادست:

public class DelegateCommand : ICommand
{
 private readonly Action<object> _executeAction;
public DelegateCommand(Action<object> executeAction)
{
_executeAction = executeAction;
}
public void Execute(object parameter) => _executeAction(parameter);
 public bool CanExecute(object parameter) => true;
public event EventHandler CanExecuteChanged;
}

برای مثال، در این پیاده سازی ساده، زمانی که Command اجرا می شود، تابع DelegateCommand فرا خوانده میشود.

حالا می توانیم کد View Model را ادامه بدهیم:

public class ViewModel : ViewModelBase
{
 ...
private readonly DelegateCommand _changeNameCommand;
public ICommand ChangeNameCommand => _changeNameCommand;
public ViewModel()
 {
_changeNameCommand = new DelegateCommand(Name);
}
private void Name(object commandParameter)
{
FirstName = &quotPouria&quot
 }
}

حال اگر برنامه را اجرا کنیم و بر روی دکمه کلیک کنیم، مقدار Text Block تغییر خواهد کرد.

حالا پخش CanExecute از ICommand را اجرا می کنیم:

public class DelegateCommand : ICommand
{
private readonly Action<object> _executeAction;
private readonly Func<object, bool> _canExecuteAction;
public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecuteAction)
{
 _executeAction = executeAction;
 _canExecuteAction = canExecuteAction;
 }
public void Execute(object parameter) => _executeAction(parameter);
public bool CanExecute(object parameter) => _canExecuteAction?.Invoke(parameter) ?? true;
public event EventHandler CanExecuteChanged;
  public void InvokeCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

همانند تابع Execute، این Command هم یک CanExecute delegate دریافت می کند. بر همین اساس، CanExecuteChanged event هم با یک متد Public اجرا می شود بنابراین ما میتوانیم هر زمان که CanExecute delegate تغییر کند آن را فرا بخوانیم.



اگرچه معماری MVVM نیاز به یادگیری بیشتر برای فهم آن نیاز دارد، اما زمانی که به مفاهیم پایه آن مسلط شوید، می تواند ساخت برنامه با WPF را بسیار راحت تر بکند. علاوه بر این موضوع، با جدا کردن قسمت های مختلف برنامه، در نهایت به یک برنامه با قابلیت نگه داری بالاتر دست پیدا می کنید که انجام اعملیات Unit test را بسیار ساده تر میکند.




Sources:

- Microsoft.com
- Intellitect.com
- Blogsnook.com
- Special thanks to Stackoverflow & Github community


روش پژوهش و ارائه مطالب - دکتر یعقوبی
آرمان برخوردار - دانشگاه صدرا