Model-View-Presenter — Википедия

Model-View-Presenter
Model View Presenter
Описан в Design Patterns Нет

Model-View-Presenter (MVP) — шаблон проектирования, производный от MVC, который используется в основном для построения пользовательского интерфейса.

Элемент Presenter в данном шаблоне берёт на себя функциональность посредника (аналогично контроллеру в MVC) и отвечает за управление событиями пользовательского интерфейса (например, использование мыши) так же, как в других шаблонах обычно отвечает представление.

Описание шаблона

[править | править код]

MVP — шаблон проектирования пользовательского интерфейса, который был разработан для облегчения автоматического модульного тестирования и улучшения разделения ответственности в презентационной логике (отделения логики от отображения):

  • Модель (англ. Model) предоставляет данные для пользовательского интерфейса.
  • Представление (англ. View) реализует отображение данных (Модели) и маршрутизацию пользовательских команд или событий Presenterʼу.
  • Presenter управляет Моделью и Представлением. Например извлекает данные из Модели и форматирует их для отображения в Представлении.

Обычно экземпляр Представления создаёт экземпляр Presenterʼа, передавая ему ссылку на себя. При этом Presenter работает с Представлением в абстрактном виде, через его интерфейс. Когда вызывается событие Представления, оно вызывает конкретный метод Presenterʼа, не имеющего ни параметров, ни возвращаемого значения. Presenter получает необходимые для работы метода данные о состоянии пользовательского интерфейса через интерфейс Представления и через него же передаёт в Представление данные из Модели и другие результаты своей работы.

public class MyModel {     private int _state = 0;     public MyModel(int initState)     {         _state = initState;     }     public int getState()     {         return _state;     } } public class MyView : IView {     private readonly MyPresenter presenter;     public MyView()     {         presenter = new(this);     } } public class MyPresenter {     private readonly IView _view;     private readonly MyModel model = new(123);      public MyPresenter(IView view)     {         _view = view;     } } 

Количество логики, допустимой в Представлении, различается для разных реализаций.

С точки зрения многоуровневой модели приложений в ООП, Presenter может рассматриваться как самостоятельный уровень между уровнем приложения и уровнем пользовательского интерфейса. В структуре Решения Приложения, Согласно принципам ООП и SOLID, каждый слой должен быть отдельной сборкой или даже набором сборок. Пример выше не позволяет построить полностью абстрактную структуру, что нарушает принципы. Например, любое изменение в Model потребует перекомпиляции Презентера, а его компиляция потребует компиляции Представления. Для устранения таких зависимостей, нарушающих принципы абстракции, Презентер должен работать с Моделью и Представлением через их интерфейсы в отдельных сборках, сборки Модели, Презентера и Представления не должны иметь ссылок друг на друга. Создаются слои и внедряются зависимости в сборке Приложения. Например, метод Main(), который является точкой запуска консольного приложения.

namespace AssembleOfInterfaces  {     public interface IModel     {         IList<int> Numbers { get; }         int Sum();     }     public interface IView     {         int ReadA();         int ReadB();         void WriteSum(int sum);     } } 
using AssembleOfInterfaces; namespace AssembleOfModel {     public class MyModel : IModel     {         public IList<int> Numbers { get; } = new List<int>();         public int Sum() => Numbers.Sum();     } } 
using AssembleOfInterfaces; using static System.Console; namespace AssembleOfView {     public class MyView : IView     {         public int ReadA()         {             Write("Коэффициент A: ");             return int.Parse(ReadLine() ?? string.Empty);         }          public int ReadB()         {             Write("Коэффициент B: ");             return int.Parse(ReadLine() ?? string.Empty);         }          public void WriteSum(int sum) => WriteLine($"Сумма коэффициентов = {sum}.");     } } 
using AssembleOfInterfaces; namespace AssembleOfPresenter {     public class MyPresenter     {         private readonly IView view;         private readonly IModel model;          public MyPresenter(IModel myModel, IView view)         {             this.view = view;             model = myModel;         }          public void Start()         {             model.Numbers.Add(view.ReadA());             model.Numbers.Add(view.ReadB());             view.WriteSum(model.Sum());         }     } } 
using AssembleOfInterfaces; using AssembleOfModel; using AssembleOfView; using AssembleOfPresenter; namespace AssembleOfApplication {     public class App     {         public static void Main()         {             MyModel model = new();             MyView view = new();             MyPresenter presenter = new MyPresenter(model, view);             presenter.Start();         }     } } 

Для тех же Модели и Презентера можно создать другое Представление. Например, Форма с двумя полями, двумя кнопками и лейблом для вывода результата. В начальном состоянии поля и кнопки отключены: Enabled=false. Code Behind формы:

using AssembleOfInterfaces; using System; using System.Threading; using System.Windows.Forms;  namespace AssembleOfFormsView {     public partial class NumbersForm : Form, IView     {         public NumbersForm()         {             InitializeComponent();             labelSum.Text = "0";         }          private readonly AutoResetEvent readAEvent = new AutoResetEvent(false);         private readonly AutoResetEvent readBEvent = new AutoResetEvent(false);         private int a, b;         public int ReadA()         {             Invoke(new Action(() => textBoxA.Enabled = btnA.Enabled = true));             readAEvent.WaitOne();             return a;         }           public int ReadB()         {             Invoke(new Action(() => textBoxB.Enabled = btnB.Enabled = true));             readBEvent.WaitOne();             return b;         }          private void btnA_Click(object sender, EventArgs e)         {             a = int.Parse(textBoxA.Text);             textBoxA.Enabled = btnA.Enabled = false;             readAEvent.Set();         }          private void btnB_Click(object sender, EventArgs e)         {             b = int.Parse(textBoxB.Text);             textBoxB.Enabled = btnB.Enabled = false;             readBEvent.Set();         }          public void WriteSum(int sum)         {             Invoke(new Action(() => labelSum.Text = sum.ToString()));         }     } } 

Точка сборки:

using AssembleOfFormsView; using AssembleOfModel; using AssembleOfPresenter; using System; using System.Threading.Tasks; using System.Windows.Forms;  namespace App {     static class Program     {         /// <summary>         /// Главная точка входа для приложения.         /// </summary>         [STAThread]         static void Main()         {             Application.EnableVisualStyles();             Application.SetCompatibleTextRenderingDefault(false);              MyModel model = new MyModel();             NumbersForm view = new NumbersForm();             MyPresenter presenter = new MyPresenter(model, view);              view.Shown += async delegate { await Task.Run(presenter.Start); };              Application.Run(view);          }     } } 

Эволюция и несколько вариантов шаблона MVP, в том числе отношения MVP с другими шаблонами проектирования (такими как MVC) были подробно проанализированы в статье Мартина Фаулера[1][2] , а также в статье Дерека Грира[3].

Примечания

[править | править код]
  1. «GUI Architectures» Мартин Фаулер. Часть 1. Дата обращения: 30 мая 2012. Архивировано 11 марта 2013 года.
  2. «GUI Architectures» Мартин Фаулер. Часть 2. Дата обращения: 30 мая 2012. Архивировано 11 марта 2013 года.
  3. «Interactive Application Architecture Patterns» Derek Greer. Дата обращения: 30 мая 2012. Архивировано 30 мая 2012 года.