Шаблон проектирования Model-View-Controller
Концепция Model-View-Controller
Model-view-controller (MVC, «Модель-представление-поведение», «Модель-представление-контроллер») — схема использования нескольких шаблонов проектирования, с помощью которых модель данных приложения, пользовательский интерфейс и взаимодействие с пользователем разделены на три отдельных компонента так, что модификация одного из компонентов оказывает минимальное воздействие на остальные. Данная схема проектирования часто используется для построения архитектурного каркаса, когда переходят от теории к реализации в конкретной предметной области[1].
Содержание
1 История
2 Назначение
3 Концепция
4 Наиболее частые ошибки
5 Пример контроллера MVC в рамках PHP5
6 Model-View-Controller-Language (MVC-L)
7 Пример реализации MVC-L
8 См. также
9 Примечания
10 Литература
11 Ссылки
Концепция MVC была описана в 1979 году[2] Трюгве Реенскаугом (англ. Trygve Reenskaug), тогда работающим над языком программированияSmalltalk в Xerox PARC. Оригинальная реализация описана в статье «Applications Programming in Smalltalk-80: How to use Model-View-Controller»
. Затем Джим Алтофф с командой разработчиков реализовали версию MVC для библиотеки классов Smalltalk-80.
В оригинальной концепции была описана сама идея и роль каждого из элементов: модели, представления и контроллера. Но связи между ними были описаны без конкретизации. Кроме того, различали две основные модификации:
Пассивная модель — модель не имеет никаких способов воздействовать на представление или контроллер, и используется ими в качестве источника данных для отображения. Все изменения модели отслеживаются контроллером и он же отвечает за перерисовку представления, если это необходимо. Такая модель чаще используется в структурном программировании, так как в этом случае модель представляет просто структуру данных, без методов их обрабатывающих.
Активная модель — модель оповещает представление о том, что в ней произошли изменения, а представления, которые заинтересованы в оповещении, подписываются на эти сообщения. Это позволяет сохранить независимость модели как от контроллера, так и от представления.
Классической реализацией концепции MVC принято считать версию именно с активной моделью.
С развитием объектно-ориентированного программирования и понятия о шаблонах проектирования был создан ряд модификаций концепции MVC, которые при реализации у разных авторов могут отличаться от оригинальной. Так, например, Эриан Верми в 2004 году описал пример обобщенного MVC[4].
Назначение
Основная цель применения этой концепции состоит в разделении бизнес-логики (модели) от её визуализации (представления, вида). За счет такого разделения повышается возможностьповторного использования. Наиболее полезно применение данной концепции в тех случаях, когда пользователь должен видеть те же самые данные одновременно в различных контекстах и/или с различных точек зрения. В частности, выполняются следующие задачи:
К одной модели можно присоединить несколько видов, при этом не затрагивая реализацию модели. Например, некоторые данные могут быть одновременно представлены в виде электронной таблицы, гистограммы и круговой диаграммы.
Не затрагивая реализацию видов, можно изменить реакции на действия пользователя (нажатие мышью на кнопке, ввод данных), для этого достаточно использовать другой контроллер.
Ряд разработчиков специализируется только в одной из областей: либо разрабатывают графический интерфейс, либо разрабатывают бизнес-логику. Поэтому возможно добиться того, что программисты, занимающиеся разработкой бизнес-логики (модели), вообще не будут осведомлены о том, какое представление будет использоваться.
Концепция
Концепция MVC позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента:
Модель (англ. Model). Модель предоставляет знания: данные и методы работы с этими данными, реагирует на запросы, изменяя своё состояние. Не содержит информации, как эти знания можно визуализировать.
Представление, вид (англ. View). Отвечает за отображение информации (визуализацию). Часто в качестве представления выступает форма (окно) с графическими элементами.
Контроллер (англ. Controller). Обеспечивает связь между пользователем и системой: контролирует ввод данных пользователем и использует модель и представление для реализации необходимой реакции.
Важно отметить, что как представление, так и контроллер зависят от модели. Однако модель не зависит ни от представления, ни от контроллера. Тем самым достигается назначение такого разделения: оно позволяет строить модель независимо от визуального представления, а также создавать несколько различных представлений для одной модели.
Для реализации схемы Model-View-Controller используется достаточно большое число шаблонов проектирования (в зависимости от сложности архитектурного решения), основные из которых «наблюдатель», «стратегия», «компоновщик»[5].
Наиболее типичная реализация отделяет вид от модели путем установления между ними протокола взаимодействия, используя аппарат событий (подписка/оповещение). При каждом изменении внутренних данных в модели она оповещает все зависящие от неё представления, и представление обновляется. Для этого используется шаблон «наблюдатель». При обработке реакции пользователя вид выбирает, в зависимости от нужной реакции, нужный контроллер, который обеспечит ту или иную связь с моделью. Для этого используется шаблон «стратегия», или вместо этого может быть модификация с использованием шаблона «команда». А для возможности однотипного обращения с подобъектами сложно-составного иерархического вида может использоваться шаблон «компоновщик». Кроме того, могут использоваться и другие шаблоны проектирования, например, «фабричный метод», который позволит задать по умолчанию тип контроллера для соответствующего вида.
Наиболее частые ошибки
Начинающие программисты (особенно в веб-программировании, где аббревиатура MVC стала популярна) очень часто трактуют архитектурную модель MVC как пассивную модель MVC. В этом случае модель выступает исключительно совокупностью функций для доступа к данным, а контроллер содержит бизнес-логику. В результате код моделей по факту является средством получения данных из СУБД, а контроллер представляет собой типичный модуль, наполненный бизнес-логикой, или скрипт в терминологии веб-программирования. В результате такого понимания MVC разработчики стали писать код, который Pádraic Brady, известный в кругах сообщества Zend Framework, охарактеризовал как ТТУК — «Толстые тупые уродливые контроллеры» (Fat Stupid Ugly Controllers)[6]:
Среднестатистический ТТУК получал данные из БД (используя уровень абстракции базы данных, делая вид, что это модель) или манипулировал, проверял, записывал, а также передавал данные в вид. Такой подход стал очень популярен потому, что использование таких контроллеров похоже на классическую практику использования отдельного php-файла для каждой страницы приложения.
Но в объектно-ориентированном программировании используется активная модель MVC, где модель — это не только совокупность кода доступа к данным и СУБД, а вся бизнес-логика. В свою очередь, контроллеры представляют собой лишь элементы системы, в чьи непосредственные обязанности входит приём данных из запроса и передача их другим элементам системы. Только в этом случае контроллер становится «тонким» и выполняет исключительно функцию связующего звена (glue layer) между отдельными компонентами системы.
Пример контроллера MVC в рамках PHP5
[PHP]<?php
class Krugozor_Module_Advert_Controller_List extends Krugozor_Module_Advert_Controller_Common
{
public function run()
{
$this->getView()->loadI18n('Common/General', 'Advert/List');
$this->initTitle();
if (!$this->checkAccess())
{
return $this->createNotification()
->setMessage($this->getView()->lang['notification']['forbidden_access'])
->setType('alert')
->setNotificationUrl('/admin/')
->run();
}
$list = new Krugozor_Module_Advert_Service_List
(
$this->getRequest(), $this->getMapper('Advert/Advert'), new Krugozor_Pagination_Manager(10, 10, $this->getRequest())
);
$this->getView()->adverts = $list->getList();
$this->getView()->pagination = $list->getPagination();
$this->getView()->id_category = $this->getRequest()->getRequest('id_category');
$this->getView()->id_user = $this->getRequest()->getRequest('id_user');
return $this->getView();
}
}
?>
Данный пример иллюстрирует реальный код контроллера на PHP5 в рамках объектно-ориентированного приложения, построенного по концепции MVC. Здесь контроллер выступает в роли класса с единственным методом run(), который запускает Front-Controller. Цель контроллера — отдать в Представление (View) данные для формирования страницы со списком объявлений.
В начале с помощью метода контроллера getView() программист получает объект Представления (View), который вызывает методы, специфичные для Представления (View): метод loadI18n()с помощью которого в Представление (View) подгружаются файлы интернационализации, а также метод initTitle(), формирующий title HTML-страницы на основе данных, полученных в ходе разбора файлов интернационализации.
Далее через метод контроллера checkAccess() проверяется, имеет ли пользователь доступ к этой странице. Если доступ не разрешён, формируется предупреждение и происходит перенаправление на ресурс «/admin/». Далее инстанцируется Модель (Model), а точнее, один из компонентов модели — сервис Krugozor_Module_Advert_Service_List. Данный класс инкапсулирует логику получения массива объектов моделей на основе различных параметров из объекта запроса Request, а также взаимодействует с объектом пагинации.
После получения данных от сервиса в Представление (View) передается массив моделей adverts, объект пагинации pagination, идентификатор запрошенной в URL-адресе категорииid_category и идентификатор пользователя id_user, после чего Представление (View) отдается во Front-Controller для дальнейшего действия (шаблонизации и вывод в выходной поток).
В примере явно видно, что Контроллер представляет собой связующее звено между двумя основными компонентами системы — Моделью (Model) и Представлением (View). Модель ничего «не знает» о Представлении, а Контроллер не содержит в себе какой-либо бизнес-логики.
Model-View-Controller-Language (MVC-L)
Model-View-Controller-Language (MVC-L, иногда MVC(L)) - расширенная версия концепции MVC с возможностью применения мультиязычности в объектах Представления (View). Одним из примеров реализации концепции MVCL является CMS OpenCart[7] , где наряду с каталогами model, view, controller (назначение каталогов соответствует схеме MVC) присутствует каталог language[8]с возможностью вывода элементов Представления (View) на любом языке.
Сравнение аналогичных частей кода в файлах локализации в OpenCart. language/russian/russial.php
<?php
$_['button_add_address'] = 'Добавить адрес';
$_['button_back'] = 'Назад';
$_['button_continue'] = 'Продолжить';
$_['button_cart'] = 'Купить';
$_['button_compare'] = 'В сравнение';
$_['button_wishlist'] = 'В закладки';
?>
language/english/english.php
<?php
$_['button_add_address'] = 'Add Address';
$_['button_back'] = 'Back';
$_['button_continue'] = 'Continue';
$_['button_cart'] = 'Add to Cart';
$_['button_compare'] = 'Add to Compare';
$_['button_wishlist'] = 'Add to Wish List';
?>
Обработка Локализации (Language) производится Контролером (Controller) перед передачей данных в Представление (View) путем извлечения текста из файла локализации и превращением их в значения переменных. Такая модель удобна для реализации мультиязычности, т.к. избавляет от необходимости исправления кода Представления (View) и переменных Котроллера (Controller) каждый раз, когда вам нужно добавить новую локализацию.
Пример реализации MVC-L
Часть кода Контроллера (Controller), отвечающая за подключение файла локализации в новом модуле, устанавливаемого в OpenCart
<?php
$this->load->language('module/my_module');
?>
После подключения файла локализации в Контроллере (Controller), присваиваем значение переменной text в массиве data из файла локализации:
<?php
$this->data['text'] = $this->language->get('text');
?>
Значение переменной $text будет взято из строчки файла локализации, соответствующее:
<?php
$_['text'] = 'Текстовая информация';
?>
Теперь результатом обработки части кода Представления (View)
<p><?php echo $text; ?></p>
Будет
<p>Текстовая информация</p>