Хичээлээр анх IBM боловсруулан гаргасан Model View Presenter (MVP) - хэвийн талаар үзнэ. MVP нь MVC загвараас үүдэлтэй боловч програмын зохион байгуулалт талаасаа нилээд өөр хандлагатай. MVP паттерн MVC -г бодвол үзүүлбэр моделтой нягт холбоогүй байдаг онцлогтой. Доор зурагт MVP хэвийн бүтцийг харууллаа.
Зургаас харахад контролерын оронд Presenter орж ирэн хэрэглэгчийн оруулсан өгөгдлийн шилжилт, загварын өөрчлөлтийг үзүүлбэрт тусгах үүргийг хариуцах болсон нь харагдана.
Presenter үзүүлбэртэй интерфейсээр дамжин харьцсанаар тестлэх боломжийг сайжруулна. Учир нь загварыг модулийн тестэд зориулагдсан тусгай загвараар сольж болно. MVP хэвийг MVC гийн адилаар програмын бизнес логик талаас авч үзцгээе.
Бизнес логикийн талаас харвал MVP нь програмын бүхий л бизнес логикийг загварт багтаан харин өгөгдөлд хандалтын кодыг загвараас тусгаарласан нь MVC тэй адилхан. Энэ нь MVP нь MVC загвараас үүдэлтэйг батлана. MVP -д зөвхөн үзүүлбэрийн логик л өөр болсон.
Өмнөх хичээлүүдэд үзсэнтэй ижилхэн WPF програмын жишээг MVP паттернг ашиглан хийцгээе. Програмын бүтцийг дараах UML диаграмаар харууллаа.
Үзүүлбэр IProjectsView интерфейсийг хэрэгжүүлэх ProjectsView классыг ашиглана. ProjectsView ба IProjectsView -д үзүүлбэрийг шинэчлэх, Presenter - тэй холбогдоход шаардлагатай бүхий л аргууд агуулагдана. Харин Presenter дотроо IProjectsView , IProjectsModel интерфейсийн төрлүүдийн холбоосыг ProjectsPresenter.view , ProjectsPresenter.model хэлбэрийн хувьсагч /шинж/ байдлаар агуулсан. Ийм зохион байгуулалт модел болон үзүүлбэрийн хоорондын харилцааг Presenter -ээр дамжуулан хийх боломжийг олгоно.
Бүтэц хэрхэн ажиллахыг тайлбарлая. Хэрвээ хэрэглэгч Update товчийг дарвал моделийг шинэчлэх ProjectsPresenter.view_ProjectUpdated() аргыг дуудах IProjectView.ProjectUpdated үйл явдлын боловсруулагч ажиллана. Загвар шинэчлэгдэх үед Presenter -ээр дамжин хэрэглэгчийн графикийн интерфейсийг шинэчлэх үйл явдлын IProjectsModel.ProjectUpdated боловсруулагч ажиллах юм. Эхэндээ загварын ажиллагааг ойлгоход хүндрэлтэй байж магадгүй. Түвшин хоорондын хамаарлыг бага болгохын тулд интерфейсийг хэрхэн ашиглаж байгааг сайн ойлгон аваарай. Ийм бүтцээр бичигдсэн програмыг цаашид хөгжүүлэх, кодыг ойлгоход цаг хугацаа, хүч хөдөлмөр хөнгөлдөг тул аргачлалыг өөрийн ажилдаа ашиглаж сурах хэрэгтэй. Бид үзүүлбэрийг үүсгэх логикийг Presenter -т шилжүүлсэн тул MVC загварыг бодвол тестлэх боломжоор хамаагүй илүү. MVC хэвд үзүүлбэрийг моделоос шууд үүсгэн өгдөг.
За одоо програмаа зохиоё. Програм MVC паттерн хичээлд үзсэнтэй яг адилхан ажиллана.
Модел
Visual Studio -д ProjectBilling.UI.MVP нэртэй WPF шинэ төсөл үүсгээд Графикийн паттерн хичээлд үүсгэсэн ProjectBilling.DataAccess төслийн холбоосыг нэмэн өгөөрэй. Бид шинэ шийдэл /solution/ үүсгэсэн болохоор ProjectBilling.DataAccess төслийн холбоос үүсгэхийн тулд шийдэлд төслийг нэмэн оруулах хэрэгтэй. Үүний тулд шийдлийн нэр дээр баруун даралт хийхэд гарч ирэх цэснээс Add->Existing Project гэж ороод ProjectBilling.DataAccess төслийг заан өгнө. Төсөл шийдэлд нэмэгдсэний дараа ProjectBilling.UI.MVP төслийн Reference дээр баруун даралт хийгээд Add Reference цэсээр ороод доорх зурагт үзүүлснээр ProjectBilling.DataAccess төслийн холбоосыг нэмнэ.
ProjectBilling.UI.MVP төсөлд дараах агуулга бүхий ProjectsModel классыг нэмнэ.
using System;
using System.Collections.Generic;
using System.Linq;
using ProjectBilling.DataAccess;
namespace ProjectBilling.Business
{
#region ProjectsEventArgs
public class ProjectEventArgs : EventArgs
{
public Project Project { get; set; }
public ProjectEventArgs(Project project)
{
Project = project;
}
}
#endregion ProjectsEventArgs
public interface IProjectsModel
{
void UpdateProject(Project project);
IEnumerable<Project> GetProjects();
Project GetProject(int Id);
event EventHandler<ProjectEventArgs> ProjectUpdated;
}
public class ProjectsModel : IProjectsModel
{
private IEnumerable<Project> projects = null;
public event EventHandler<ProjectEventArgs>
ProjectUpdated = delegate { };
public ProjectsModel()
{
projects = new DataServiceStub().GetProjects();
}
public void UpdateProject(Project project)
{
ProjectUpdated(this,
new ProjectEventArgs(project));
}
public IEnumerable<Project> GetProjects()
{
return projects;
}
public Project GetProject(int Id)
{
return projects.Where(p => p.ID == Id)
.First() as Project;
}
}
}
Моделийн класс ProjectsModel доорх гишүүдтэй IProjectsModel интерфейсийг хэрэгжүүлж байгаа.
- UpdateProject() арга төслийг шинэчлэнэ. Аргыг төлвөөр дамжуулан шинэчлэлийг хийж болохоор өргөжүүлж болох ч энд энгийн байлгах үүднээс үүнийг авч үзээгүй.
- GetProjects() төслүүдийн жагсаалтыг авчирна.
- GetProject() төслийг ID -гаар нь буцаана
- ProjectUpdated төсөлд өөрчлөлт ороход дуудагдах үйл явдал.
ProjectEventArgs бол гишүүдийг нь үйл явдлын ProjectUpdated боловсруулагч ашиглаж болох туслах класс
Үзүүлбэр
Үзүүлбэрийг ProjectView.xaml , ProjectView.xaml.cs файлууд үүсгэнэ. Төсөлд үзүүлбэрийг нэмэхийн тулд төслийн нэр дээр баруун даралт хийхэд гарч ирэх цэснээс Add->Window гэж ороод үзүүлбэрийн нэрээ өгөхөд VS засварлагч .xaml, .cs өргөтгөлтэй файлуудыг төсөлд нэмэн оруулна. ProjectView.xaml файл дараах агуулгатай байна.
<Window x:Class="ProjectBilling.UI.MVP.ProjectsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Төслүүд" MinHeight="265" Height="265"
MinWidth="250" Width="250">
<StackPanel>
<Label Content="Төсөл:" Margin="5, 0, 5, 0" />
<ComboBox Name="projectsComboBox" Margin="5, 0, 5, 0"
SelectionChanged="projectsComboBox_SelectionChanged" />
<Label Content="Нэр:" Margin="5, 0, 5, 0" />
<TextBox Name="nameTextBox" Margin="5, 0, 5, 0"
IsEnabled="False" />
<Label Content="Тооцооны өртөг:" Margin="5, 0, 5, 0" />
<TextBox Name="estimatedTextBox" Margin="5, 0, 5, 0"
IsEnabled="False" />
<Label Content="Гүйцэтгэлийн өртөг:" Margin="5, 0, 5, 0" />
<TextBox Name="actualTextBox" Margin="5, 0, 5, 0"
IsEnabled="False" />
<Button Name="updateButton" Content="Update"
Margin="5, 10, 5, 0" IsEnabled="False"
Click="updateButton_Click" />
</StackPanel>
</Window>
Формын кодыг үзүүлбэл
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using ProjectBilling.Business;
using ProjectBilling.DataAccess;
namespace ProjectBilling.UI.MVP
{
public interface IProjectsView
#region IProjectsView
{
int NONE_SELECTED { get; }
int SelectedProjectId { get; }
void UpdateProject(Project project);
void LoadProjects(IEnumerable<Project> projects);
void UpdateDetails(Project project);
void EnableControls(bool isEnabled);
void SetEstimatedColor(Color? color);
event EventHandler<ProjectEventArgs> ProjectUpdated;
event EventHandler<ProjectEventArgs> DetailsUpdated;
event EventHandler SelectionChanged;
}
#endregion IProjectsView
#region ProjectsView
public partial class ProjectsView : Window, IProjectsView
{
#region Initialization
public int NONE_SELECTED { get { return -1; } }
public event EventHandler<ProjectEventArgs>
ProjectUpdated = delegate { };
public int SelectedProjectId { get; private set; }
public event EventHandler SelectionChanged
= delegate { };
public event EventHandler<ProjectEventArgs>
DetailsUpdated = delegate { };
public ProjectsView()
{
InitializeComponent();
SelectedProjectId = NONE_SELECTED;
}
#endregion Initialization
#region Event handlers
private void updateButton_Click(object sender,
RoutedEventArgs e)
{
Project project = new Project();
project.Name = nameTextBox.Text;
project.Estimate =
GetDouble(estimatedTextBox.Text);
project.Actual =
GetDouble(actualTextBox.Text);
project.ID =
int.Parse(
projectsComboBox.SelectedValue.
ToString());
ProjectUpdated(this,
new ProjectEventArgs(project));
}
private void projectsComboBox_SelectionChanged(
object sender, SelectionChangedEventArgs e)
{
SelectedProjectId
= (projectsComboBox.SelectedValue == null)
? NONE_SELECTED
: int.Parse(
projectsComboBox.SelectedValue.
ToString());
SelectionChanged(this,
new EventArgs());
}
#endregion Event handlers
#region Public methods
public void UpdateProject(Project project)
{
IEnumerable<Project> projects =
projectsComboBox.ItemsSource as
IEnumerable<Project>;
Project projectToUpdate =
projects.Where(p => p.ID == project.ID)
.First() as Project;
projectToUpdate.Name = project.Name;
projectToUpdate.Estimate = project.Estimate;
projectToUpdate.Actual = project.Actual;
if (project.ID == SelectedProjectId)
UpdateDetails(project);
}
public void LoadProjects(IEnumerable<Project> projects)
{
projectsComboBox.ItemsSource = projects;
projectsComboBox.DisplayMemberPath = "Name";
projectsComboBox.SelectedValuePath = "ID";
}
public void EnableControls(bool isEnabled)
{
estimatedTextBox.IsEnabled = isEnabled;
actualTextBox.IsEnabled = isEnabled;
updateButton.IsEnabled = isEnabled;
}
public void SetEstimatedColor(Color? color)
{
estimatedTextBox.Foreground
= (color == null)
? actualTextBox.Foreground
: new SolidColorBrush((Color)color);
}
public void UpdateDetails(Project project)
{
nameTextBox.Text = project.Name;
estimatedTextBox.Text
= project.Estimate.ToString();
actualTextBox.Text
= project.Actual.ToString();
DetailsUpdated(this,
new ProjectEventArgs(project));
}
#endregion Public methods
#region Helpers
private double GetDouble(string text)
{
return string.IsNullOrEmpty(text)
? 0 : double.Parse(text);
}
#endregion Helpers
}
#endregion ProjectsView
}
байна. Кодын бүтэц дээр үзүүлсэн UML диаграмтай тохирно.
Presenter
Presenter -ыг ердийн класс нэмэх байдлаар оруулна. Харин классын нэрийг ProjectsPresenter гэж өгөөд доорх агуулгыг оруулан өгөөрэй.
using System;
using System.Windows.Media;
using ProjectBilling.Business;
using ProjectBilling.DataAccess;
namespace ProjectBilling.UI.MVP
{
public class ProjectsPresenter
{
#region Initialization
private IProjectsView view = null;
private IProjectsModel model = null;
public ProjectsPresenter(IProjectsView projectsView,
IProjectsModel projectsModel)
{
view = projectsView;
view.ProjectUpdated += view_ProjectUpdated;
view.SelectionChanged
+= view_SelectionChanged;
view.DetailsUpdated += view_DetailsUpdated;
model = projectsModel;
model.ProjectUpdated += model_ProjectUpdated;
view.LoadProjects(
model.GetProjects());
}
#endregion Initialization
#region Event handlers
private void view_DetailsUpdated(object sender,
ProjectEventArgs e)
{
SetEstimatedColor(e.Project);
}
private void view_SelectionChanged(object sender,
EventArgs e)
{
int selectedId = view.SelectedProjectId;
if (selectedId > view.NONE_SELECTED)
{
Project project =
model.GetProject(selectedId);
view.EnableControls(true);
view.UpdateDetails(project);
SetEstimatedColor(project);
}
else
{
view.EnableControls(false);
}
}
private void model_ProjectUpdated(object sender,
ProjectEventArgs e)
{
view.UpdateProject(e.Project);
}
private void view_ProjectUpdated(object sender,
ProjectEventArgs e)
{
model.UpdateProject(e.Project);
SetEstimatedColor(e.Project);
}
#endregion Event handlers
#region Helpers
private void SetEstimatedColor(Project project)
{
if (project.ID == view.SelectedProjectId)
{
if (project.Actual <= 0)
{
view.SetEstimatedColor(null);
}
else if (project.Actual
> project.Estimate)
{
view.SetEstimatedColor(Colors.Red);
}
else
{
view.SetEstimatedColor(Colors.Green);
}
}
}
#endregion Helpers
}
}
Энд үйл явдлын дараах боловсруулагчдыг тодорхойлсон.
- view_DetailsUpdated боловсруулагч IProjectsView.DetailsUpdated үйл явдлын хариу байдлаар дуудагдах бөгөөд төслийн гүйцэтгэлийн өртөгийг харуулах TextBox -ийн өнгийг шинэчлэх SetEstimateColor() аргыг дуудна.
- view_SelectionChanged нь IProjectsView.SelectionChanged үйл явдал үүсэхэд үзүүлбэрийг өөрчилнө. Үйл явдал ComboBox -д шинэ төслийг сонгоход үүснэ.
- model_ProjectUpdated боловсруулагч IProjectsModel.ProjectUpdate -ийн хариуд дуудагдан модел ба үзүүлбэрийн шинэчлэлтийг ажиллуулна.
Хэрвээ та хэвүүд ашиглан програм зохиож байгаагүй бол код ойлгомжгүй байж мэднэ. C# хэлний мэдлэгтэй бол энд бичсэн кодууд их энгийн учраас юу хийж байгааг ойлгоход амархан. Хамгийн гол зүйл бол паттерныг ашиглан програмын кодыг хэрхэн зохион байгуулж байгааг сурах юм. Хэвийг ОХП-ын ямарч хэлэнд ашиглаж болно.
Одоо програмын эхлэлийн цонх MainWindow.xaml -аас ProjectsView үзүүлбэрийг нээх боломжтой болгохын тулд MainWindow.xaml -д доорх өөрчлөлтийг оруулъя.
<Window x:Class="ProjectBilling.UI.MVP.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="250" Width="250"
MinHeight="250" MinWidth="250">
<StackPanel>
<Button Content="Төслийг үзүүлэх"
Name="showProjectsButton" Margin="5, 10, 5, 0"
Click="showProjectsButton_Click" />
</StackPanel>
</Window>
Үндсэн цонхонд төслийн үзүүлбэрийг харуулах товч нэмсэн тул MainWindow.cs файлын агуулгыг доорх байдлаар өөрчлөөрэй.
using System.Windows;
using ProjectBilling.Business;
namespace ProjectBilling.UI.MVP
{
public partial class MainWindow : Window
{
private IProjectsModel model = null;
public MainWindow()
{
InitializeComponent();
model = new ProjectsModel();
}
private void showProjectsButton_Click(object sender,
RoutedEventArgs e)
{
ProjectsView view = new ProjectsView();
ProjectsPresenter presenter
= new ProjectsPresenter(view, model);
view.Owner = this;
view.Show();
}
}
}
Програмыг ажлуулбал MVC паттерн хичээлд хийсэн жишээтэй ижилхэн үр дүн гарч ирнэ. Энд MVP -д ашиглаж байгаа Presenter бүтэц л чухал. Танд ийм амархан зүйлийг хэтэрхий их ажил нэмэн хийгээд байгаа мэт санагдаж магадгүй. Сургалтын жишээнд л ингэж санагдана. Програм том хэмжээний олон асуудлыг шийдэх хэрэгтэй болоход хэвийн ач тус мэдрэгдэнэ. Хэвийн ерөнхий бүтцийг гаргах ажиллагаа нь таны програмын том жижгээс хамаарахгүй учраас л тэгж санагдаж байгаа юм. Цааш програмын хэмжээ томрох үед энэ загвараа ашиглаад явах болохоор асуудал багатай. MVC -тэй харьцуулбал MVP нилээд дэвшлийг авчирсан.
- MVP нь Presenter -ээр дамжуулан төлвийн шалгалт, үзүүлбэрийн логикийг удирдах боломжийг өгсөн.
- Үзүүлбэрийг моделоос бүрэн салгаж тэдгээрийн холбоог Presenter -ээр дамжуулан зохион байгуулсан.
MVP нь MVC гээс ялгаатай нь IView интерфейсийг ашигласнаар загварт шууд өөрчлөлт хийхгүйгээр бизнес логикийг дахин ашиглах боломжийг олгодог. Жишээ нь та энэ WPF програмыг Silverlight рүү шилжүүлэх хэрэгтэй болбол Silverlight дээр IProjectsView интерфейсийг хэрэгжүүлсэн үзүүлбэрийг л зохиох хэрэгтэй. Харин IProjectsPresenter, IProjectsModel интерфейсүүдийг өөрчлөлтгүйгээр дахин ашиглаж болно.
Хичээлийг сайн судлаад ерөнхий санааг сайн ойлгож аваарай.