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].
См. также
- Model-View-Controller
- Бизнес-логика
Примечания
- ↑ «GUI Architectures» Мартин Фаулер. Часть 1 (неопр.). Дата обращения: 30 мая 2012. Архивировано 11 марта 2013 года.
- ↑ «GUI Architectures» Мартин Фаулер. Часть 2 (неопр.). Дата обращения: 30 мая 2012. Архивировано 11 марта 2013 года.
- ↑ «Interactive Application Architecture Patterns» Derek Greer (неопр.). Дата обращения: 30 мая 2012. Архивировано 30 мая 2012 года.
Ссылки
- Model-View-Controller в .Net
- Large scale application development and MVP