Программы
Шаблон разработки ПО — Model View Controller (MVC)

Шаблон разработки ПО — Model View Controller (MVC)

MVC - один из самых распространённых архитектурных шаблонов разработки. Часто используется в различных фреймворках. В том числе и в Django.

Model-View-Controller, или же "Модель-Представление-Контроллер" - это паттерн (шаблон), рекомендующий разделять приложение на три части (в некотором смысле - также "трёхзвенная архитектура"). В сущности, приложение рекомендуется разделить на:

  • Модель - для изолирования логики работы с данными.
  • Представление - на этом уровне изолируется взаимодействие с пользователем.
  • Контроллер - содержит логику, необходимую для интерпретации пользовательских действий и отражения их в модели. И наоборот.

В своё время, как и многие замечательные открытия 70-х / 80-х годов (тот же графический интерфейс, который позже появился в Apple Lisa) MVC был придуман в компании Xerox. И долгое время данный паттерн был "одним из", но при развитии веб-фреймворков, получил вторую жизнь. В частности, используется в самых популярных веб-фреймворках языков PHP, Python, Ruby - Symfony / Laravel, Django, Ruby on Rails.

Суть паттерна Model-View-Controller

Каждую хранимую логическую сущность мы можем представить в виде модели. При чём в каждой конкретной модели мы можем реализовать методы, предоставляющие удобный доступ к данным. Также можно реализовать базовую модель, от которой прочие будут наследоваться для определения общих методов, предоставляющих общий интерфейс для всех моделей - всё же для почти всех моделей будет логично иметь базовые функции: Create, Read, Update, Delete (CRUD) - создание, чтение, обновление и удаление объектов данной модели.

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

Также, определив модель для какого-то типа данных, можно использовать её для многих задач, во многих контроллерах. Таким образом мы можем переиспользовать код модели множество раз.

Опять же, имея единую точку для взаимодействия с базой данных - модель, мы можем на этом "мосту" установить "стражников" - валидацию сохраняемых данных. Это также поможет сохранить наши данные в целостности и нужном формате.

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

Слой представления отвечает за получение информации от пользователя. А значит здесь будет и первичная валидация и очистка данных, чтобы привести их к виду, в котором наша программа уже будет готова работать с ними. Поэтому тут скорее всего расположатся различные декодеры, парсеры данных, процессоры форм, троттлинг-системы, csrf-проверки и прочие "стражи", охраняющие нас от зловредов снаружи.

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

И снова - разные представления могут пользоваться разными моделями данных, более того - во время жизни проекта эти связи (через контроллер) не редко меняются. Поэтому, если мы хотим реже переписывать весь проект - лучше писать модульно, чтобы при изменении требований было нужно переписать только модуль, или же просто немного подправить его интерфейс.

Представьте, что наше приложение изначально было написано для настольных компьютеров с ОС Windows. Позднее мы решили, что выгоднее сделать web-версию. Если наш код был написан монолитом, то весь код придётся переписывать. Если же мы отделили представление данных от логики работы с данными и хранения данных, то можно будет переписать лишь ту часть, что отвечала за представление, то есть интерфейс для пользователя.

Контроллеры же - связующее звено - они принимают очищенную и преобразованную информацию от пользователя и принимают решение, что же стоит делать. В своей работе они имеют доступ к моделям, в которых будет отражен результат работы контроллеров, либо из них контроллер будет брать данные для выполнения задач, полученных от пользователя.

По хорошему, контроллеры - это связующее звено - "клей" между представлениями и моделями. Так как одна модель может использоваться для многих представлений, а одно представление может использовать сразу несколько моделей, нам нужен данный "клей" для их связи. В некотором смысле - это как в реляционных базах данных - таблица для связи многие-ко-многим.

В некоторых реализациях контроллеры - это обычные функции. В других же - набор функций, объединённых одной предметной областью, например, контроллер для регистрации, или же для галереи изображений. Если у нас класс-контроллер, в котором методы занимаются обработкой различных запросов пользователя (отобразить страницу регистрации, регистрация через форму, регистрация по приглашению и т.д.), то методы также называются action-ми (действия).

Толстые модели и простые контроллеры

Частая ошибка новичка - попытаться положить в контроллер всю логику приложения. Часто лучше логику модификации данных держать в модели. Пусть модели будут большими (толстыми), а контроллеры только клеем. Тогда при изменении связей (создании другого контроллера) в другом месте также будет доступно то преобразование модели.

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

Почти во всех ситуациях лучше держать контроллеры максимально простыми. Есть даже анти-паттерн: Толстые Тупые Уродливые Контроллеры (ТТУК). Попытка уместить логику в контроллер приводит к тому, что контроллеры, отвечающие за разные пользовательские действия будут содержать логику для работы с разными моделями в одном месте. При таком подходе модели и контроллеры слишком сильно связываются, теряется модульность.

Если ещё при этом программист в представлении будет держать только шаблонизатор/кодировщик данных, а валидацию делать в контроллере - получаем вырождение шаблона MVC в обычный монолит с шаблонизатором и адаптером к базе данных. Такой код будет сложно изменить под новые требования (а они всегда есть, если ваш проект жив).