В начале ничего не было. Затем Разработчик написал «Hello world», и программа запустилась. И Разработчик увидел, что программа хороша. Заказчик попросил Разработчика добавить новую функцию, и Разработчик это сделал. И это было хорошо. Заказчик еще раз попросил добавить новую функцию, что и сделал Разработчик. Прожорливый Заказчик попросил еще одну фичу, и тут все начало разваливаться 😁.

Мораль истории: не добавлять новую функцию?🤔 Нет! Научитесь справляться со сложностью.

Однако недостаточно знать, как справляться со сложностью. Также важно знать, когда она начинается. Точно так же, как нет никакой пользы в строительстве шоссе, когда ты уезжаешь в деревню, нет необходимости в микросервисах, когда ты занимаешься только CRUD.

Сегодня мы увидим, как развивалась архитектура. В чем разница между семейством паттернов MV*. И как они применялись в среде DotNet.

Ядро МВ*

Первые программы не имели никакой архитектуры и в основном были нацелены только на реализацию задачи. Достаточно быстро в результате получился большой ком грязи:

Еще в Древнем Риме знали принцип «разделяй и властвуй». Так родился наш первый большой архитектурный паттерн.

Возможно, вы уже слышали о шаблоне MVC. Идея очень светлая: отделить бизнес-логику от представления. Таким образом, вы не окажетесь в ситуации, когда ваш пользовательский интерфейс поддерживает только английский язык, а добавление поддержки другого языка нарушает всю бизнес-логику. Вам может показаться диким, но это пример из личного опыта.

MVC стал настолько популярен, что его начали использовать все, с небольшими отличиями, подстроенными под свои нужды и возможности своего фреймворка. Например, MVVM был представлен вместе с WPF в основном из-за механизма привязки. Единственные различия между этими шаблонами заключаются в том, что стоит между моделью и представлением, и в том, как на диаграмме рисуются стрелки.

Взгляните на эти изображения. Вы быстро заметите, что и View, и Model присутствуют во всех них. Несмотря на то, что вид — это то, как вы видите свою программу, основным и наиболее важным компонентом здесь является модель. Это сердце вашего приложения. В то время как представление настолько хрупкое, что может меняться каждый год, модель всегда останется неизменной.

Вы можете встретить различную терминологию. В основном существует два типа модели:

  • Модель данных — это просто dto
  • Модель предметной области (не класс!) — ваша бизнес-логика, ее понятия и правила

Во всех этих шаблонах MV* буква M означает Модель предметной области.

Сама модель предметной области также имеет несколько типов (на самом деле их больше, но нас интересуют только эти два):

  • Активная модель — может запускать доменное событие некоторых изменений
  • Пассивная модель — не распространяет информацию о своем состоянии

На всех приведенных выше диаграммах вы можете увидеть стрелку от модели с названием «Пожарные события». Это действительно только для активной модели. Это не так уж много, и для начинающих разработчиков (которыми я всегда являюсь) это просто делает вещи более запутанными. Поэтому не будем на этом заострять внимание. Просто не обращайте внимания на эту стрелку 😃

Итак, теперь наша упрощенная картина мира имеет следующий вид:

Не думайте, что мы говорим здесь о какой-то абстракции. Так как я уже обещал вам примеры на C#, вот вам, какой может быть пассивная доменная модель интернет-магазина:

Это пара классов, которые представляют поведение предметной области. Это ключевая концепция всех шаблонов MV*, и она останется неизменной независимо от фреймворка.

С другой стороны, Просмотр — это просто пользовательский интерфейс. Это может варьироваться в зависимости от фреймворка, поэтому вот лишь несколько примеров того, каким может быть представление: веб-страница, окно рабочего стола, консольный интерфейс, REST API и так далее.

Теперь, когда все установилось, и вы видите сходство, давайте посмотрим на отличия этих паттернов.

МВК

  • Связующим звеном между Моделью и Представлением здесь является Контроллер.
  • Контроллер получает входные данные от представления и передает их модели. Контроллер не может обновить View без взаимодействия с пользователем.
  • Примените его, когда представление отключено от программы. Отличным примером для этого может быть веб-страница, отделенная от сервера.
  • Этот шаблон активно используется ASP.

В ASP MVC out View, скорее всего, будет представлен в виде HTML-страницы. Пользователь взаимодействует с представлением, нажимая кнопку. Скорее всего, это приведет к отправке HTTP-запроса из браузера клиента на сервер:

Ввод передается контроллеру, который манипулирует моделями:

В самом конце мы могли бы запустить несколько DomainEvent и отправить их в представление с SignalR, но нет причин усложнять это. Поэтому вместо этого наш контроллер просто показывает новое представление с данными, полученными от нашей модели.

Когда вы создаете новый проект ASP, вы заметите, что папка модели содержит модели данных, а не модель предметной области. Вы не можете винить в этом Microsoft. Они создавали общий шаблон, который подходил бы для любого домена. Чтобы не оставлять эту папку пустой, они должны положить туда что угодно. Они и не подозревали, какую медвежью услугу они окажут всему сообществу разработчиков. Поскольку логику вставлять некуда, разработчики начинают не только неправильно понимать MVC, но и писать бизнес-логику прямо внутри контроллеров. Но это уже другая история…

Лучший игрок

  • Связующим звеном между Моделью и Представлением здесь является Presenter.
  • Presenter выполняет ту же работу, что и Controller. Единственная разница здесь в том, что с Presenter у нас есть доступ к просмотру и мы можем обновлять его вручную.
  • Используйте в ситуациях, когда доступ к просмотру из программы есть всегда, но нет механизма привязки.
  • Windows Forms — прекрасный тому пример.

В WinForms наше представление представляет собой набор окон, называемых формами. Это всего лишь простой класс, наследуемый от Form. Пользователь взаимодействует с представлением, нажимая кнопку, которая запускает функцию обратного вызова LoadOrdersButton_Click. Наше представление имеет прямую ссылку на Presenter и может передавать ввод без каких-либо HTTP-запросов:

И наш Presenter аналогично Контроллеру манипулирует Моделью. Ключевым моментом здесь является то, что он также имеет прямую ссылку на представление и может обновлять его, когда захочет, что дает нам живой опыт:

Еще раз, мы могли бы иметь DomainEvents, который будет передан нашему Presenter, но мы решили опустить это.

МВВМ

  • Связующим звеном между моделью и представлением здесь является ViewModel.
  • ViewModel похож на Presenter. Он имеет доступ к просмотру и может обновлять его. Единственная разница здесь в том, что нам не нужно делать это вручную. Обновление осуществляется механизмом привязки через события.
  • Используйте в ситуациях, когда доступ к просмотру из программы всегда присутствует и требуется привязка.
  • Примером, где возможен MVVM, является WPF.

Как всегда, начнем с вида. В представлении WPF также отображается окно рабочего стола, но на этот раз оно представлено xaml файлами. Пользователь взаимодействуетс View, нажимая кнопки, которые передают вызовы в ViewModel, в то время как ViewModel обновляет его обратно с помощью события. Механизм привязки элементов пользовательского интерфейса к данным, называемый привязкой:

Наша ViewModel делает то же самое, что и Controller и Presenter. Единственная разница здесь в том, что вся работа выполняется за кулисами, и нам не нужно об этом беспокоиться. В WPF привязка возможна через интерфейс INotifyPropertyChanged. Вы можете реализовать это самостоятельно или использовать структуру данных, которая уже сделала это, например ObservableCollection в нашем примере:

Краткое содержание

Конечно, это только верхушка айсберга. Эволюция архитектуры здесь не остановилась. Есть и другие закономерности для исследования, но мы уже съели большой кусок пирога, так что вы можете гордиться собой. Давайте просто остановимся здесь и посмотрим, что мы узнали 😌

Как видите, все паттерны MVC, MVP, MVVM очень похожи. Они были созданы, чтобы отделить представление от бизнес-логики. Единственная разница между тем, какой компонент стоит между View и Model, который определяется тем, сколько есть доступов из вашей программы к view.

Для активной модели у нас также будет DomainEvents. Здесь они опущены, но когда вы поймете первоначальную идею пассивной модели, вам не составит труда улучшить эти примеры.

Я надеюсь, что это было для вас поучительно, и теперь вы можете заметить разницу между этими шаблонами. Если вы все еще не можете, просто просмотрите эти примеры еще раз. Сравните сходство и различие между Controller, Presenter и ViewModel.

К вашему сведению, я не претендую на академическую достоверность, просто хотел поделиться своим видением, так что не обижайтесь на меня в разделе комментариев 😁 Или делайте это, если считаете, что я не прав 😜

Хлопайте, если вы дошли до этого момента 👋

Вы также можете поддержать меня по ссылке ниже ☕️

И не забудьте подписаться, если хотите продолжить этот путь зрелости архитектуры вместе со мной ✅