MVC (Model-View-Controller) — фундаментальный шаблон проектирования, целью которого является отделение логики интерфейса пользователя от логики программирования. MVC имеет множество вариантов реализации. Существуют модификации шаблона, самые известные из них MVP и MVVM.
Ниже описана концепция архитектурного шаблона MVC и его модификаций MVP и MVVM. Приведена концепция шаблона MVA, основанная на паттернах MVC, MVP и MVVM и адаптирована под язык ABAP.
Model-View-Controller
- Model (модель) — содержит модель данных и бизнес-логику. Иными словами, модель делает все для чего создавалась программа.
- View (представление) — интерфейс взаимодействия с пользователем. Представление отображает часть данных модели пользователю. В некоторых случаях, представление может иметь свою бизнес-логику.
- Controller (контроллер) — связывает модель и представление между собой. Контроллер обрабатывает события пользователя, управляет состоянием модели.
Точкой входа в программу является контроллер. Контроллер имеет ссылки на модель и на представление. Контроллер подписывается на события представления, а представление подписывается на события модели.
Когда пользователь совершает какие-нибудь действия, управление переходит к контроллеру и контроллер воздействует на модель.
При изменении модели идет воздействие контроллер получает соответствующее событие и воздействует на представление для обновления данных.
В реализации MVC независимой частью является модель, она ничего не знает о представлении и контроллере.
Если программа содержит несколько представлений (например, несколько ABAP-экранов), то для каждого представления требуется создать свой контроллер. При этом, допускается использовать одну и туже модель в разных контроллерах и представлениях.
Реализация MVC в ABAP подробно описана в книге Design Patterns in ABAP Objects с примерами ALV и Web Dynpro представлений.
Model-View-Presenter
Компоненты Model и View в шаблоне MVP аналогичны соответствующим компонентам модели MVC. Основной целью данного шаблона является отделения модели от представления. В шаблоне MVP информация об изменении модели поступает на презентер (Presenter), который воздействует на представление для обновления состояния.
Отсутствие связи между моделью и представлением позволяет сделать абстракцию представления. Реализовать данный паттерн можно путем выделения интерфейса представления IView. Интерфейс будет содержать набор методов и свойств, необходимых презентеру, сам презентер будет подписываться на события представления и вызывать методы для его обновления. Абстрагирование представления полезно в задачах, где требуется отобразить один и тот же набор данных в разных представлениях. Например, выводить отчет в разных форматах: ALV, PDF, EXCEL.
Model-View-ViewModel
Шаблон MVVM предназначен для разработки в WPF на языке C#. Но его идею можно применять и в ABAP. Идея данного паттерна заключается в отделении слов друг от друга. Слой View знает только о ViewModel, ViewModel знает только о Model.
Представление не требует реализации IView, для получения данных ему требуется передать ссылку на источник данных (DataContext). ViewModel является посредником между передачей данных от модели в DataContext, элементы представления ссылаются на источник данных через биндинги (Binding).
Важное отличие MVVM от MVC и MVP то, что представление в MVVM может менять модель напрямую за счет двухстороннего биндинга. В случае с MVC и MVP представление имеет доступ «только на чтение» к данным модели.
Model-View-Application
Особенностью ABAP приложений является то, то представление может обновиться только после действий пользователя. Даже если какой-нибудь асинхронный процесс поменяет модель, то инициировать обновление представление он не сможет. Данная особенность позволяет ослабить связь модель-представление и делегировать функцию обновления представления самому представлению. Иными словами, представление само должно решать, когда надо обновить себя, а когда нет.
Если взять за основу паттерны MVP и MVVM и принять во внимание отсутствие обратной связи от модели до представления, то можно спроектировать новый архитектурный шаблон MVA.
Концепция MVA
Реализация MVA основана на объектно-ориентированном подходе, где на каждый слой архитектуры будет реализован один или несколько классов. Каждый из слоев обладает рядом свойств.
Представление (View и IView):
- MVA работает с абстракцией представления IView. Все классы View должны содержать реализацию IView.
- IView содержит события, которые требуют взаимодействия с моделью
- IView содержит контекст — ссылка на данные модели, которые необходимо отобразить пользователю
- View может содержать бизнес-логику, которая не требует взаимодействия с моделью. Например, если требуется реализовать из ALV проваливание в карточку контрагента, то данная логика будет относиться к представлению.
Приложение (Application):
- Выполняет роль связки представления и модели и является точкой входа в приложение.
- Имеет критерии запуска — набор параметров, которые определяют с какими параметрами необходимо запустить приложение. Обычно это параметры селекционного экрана.
- Критерии приложения состоят из критериев модели и представления. Например, если на селекционном экране требуется ввести дату проводки и указать флаг вывода отчета PDF или ALV, то дата проводки будет относиться к критериям модели, а флаг PDF и ALV к критериям представления.
- В конструктор приложения передаются критерии запуска. Приложение создает модель и представление, подписывается на события представления, связывает контекст представления с моделью.
Модель (Model):
- Содержит публичные атрибуты, которые необходимы представлению.
- Содержит критерии расчета модели и метод инициализации.
Реализация
В коде я буду придерживаться классических обозначений MVC, за исключением контроллера. Его буду называть Application или App.
В выборе между локальными и глобальными классами предпочтение отдаю последним. Использование глобальных классов позволит отказаться от SUBMIT и CALL TRANSACTION если потребуется вызывать программу из другой разработки.
В качестве примера рассмотрим задачу вывода движений документов материала. Упрощенная версия стандартной транзакции MB51.
Модель
Поля, необходимые для отображения определим в структуре ZSMVC_DEMO_OUTTAB.
Модель будет реализована в классе ZCL_MVC_DEMO_MODEL.
Для связи модели с представлением будет публичный атрибут.
Конструктор модели будет принимать критерии выбора данных (в данном случае это критерии экрана выбора).
В модели будет публичный метод для инициализации состояния модели.
Представление
Интерфейс представления (IView) будет содержать структуру с типом контекста (Context) и методы для установки контекста и отображения представления. Причем, все компоненты контекста будут ссылками.
Для примера рассмотрим две реализации IView: вывод отчета в формате ALV и в виде списка. Классы ZCL_MVC_DEMO_VIEW_ALV и ZCL_MVC_DEMO_VIEW_LIST соответственно.
Реализация IView в виде списка во многом дублирует код View ALV. Это сделано для простоты понимания.
Приложение (контроллер)
Конструктор приложения принимает на вход критерии приложения (Criteria) и создает экземпляры Model и View, после чего связывает контекст представления с моделью. При этом, критерии приложения состоят из критериев модели и критериев представления. На этом этапе также следует подписывать приложение на события представления. Далее это будет подробно рассмотрено.
Метод RUN приложения инициализирует состояние модели и отображает представление.
Основная программа
Основная программа будет содержать селекционный экран, его PBO/PAI логику и запуск контроллера с передачей ему параметров. Мне встречались реализации, где экран выбора представлен в виде отдельного MVC приложения, но код при этом становился более сложным и громоздким.
Тип структуры Criteria определен в самом классе App, а поля структуры критериев совпадают с параметрами селекционного экрана. В данном примере, параметры F_ALV и F_LIST определяют выбор представления, остальные параметры относятся к критериям модели.
Метод RUN приложения запускает программу. Запуск приложения можно сравнить с запуском транзакции с заранее определенными параметрами экрана. Это позволяет использовать ее из других программ без SUBMIT.
Примечание. Иногда бывает удобнее передать в App не Criteria, а экземпляры Model и View. В таком случае меняются входные параметры конструктора и создание соответствующих объектов выносится за пределы приложения.
Селекционный экран
Представление в виде ALV таблицы
Представление в виде списка
Как реализовать сложную логику экрана
Проблемы классов в ABAP в том, что на них нельзя построить полноценный GUI. Ситуации, в которых стандартного CL_SALV_TABLE достаточно довольно редки. Поэтому, для определения GUI-заголовков/статусов и экранов следует создавать группу функций. ГФ будет тесно связана с View. По сути, она будет являться частью View.
Всю PBO и PAI логику должен обрабатывать класс View. При необходимости взаимодействия с Model, View должно вызывать событие, которое будет обрабатывать контроллер.
Расширение View
Допустим нам потребовалось расширить программу: добавить кнопку с обновлением данных, реализовать «проваливание» в MIGO по документу материала и в MM03 по коду ОЗМ.
Дальнейшие действия будем проводить только с ALV представлением, но на практике нужно будет адаптировать все реализации IView.
Для реализации данной задачу GUI логику перенесем в функциональный модуль Z_MVC_DEMO_VIEW_ALV_DISPLAY, который будет принимать ссылку на CL_SALV_TABLE.
В классе ZCL_MVC_DEMO_VIEW_ALV объявим обработчики событий SALV: ON_USER_COMMAND, ON_DOUBLE_CLICK. Метод DISPLAY будет создавать экземпляр CL_SALV_TABLE и подписываться на события. Управление по выводу GUI будет передаваться в Z_MVC_DEMO_VIEW_ALV_DISPLAY.
В группу функций с представлением создаем GUI статус с кнопкой «Обновить» (REFRESH). В Z_MVC_DEMO_VIEW_ALV_DISPLAY устанавливаем указанный статус и отображаем ALV.
Расширяем IView, добавляя в него событие REFRESH, которое будет обновлять модель
В контроллере делаем обработчик события Refresh и подписываемся на соответствующее событие IView.
Теперь настраиваем обработчик USER_COMMAND для View. При нажатии на кнопку «Обновить» вызываем событие обновления модели данных и обновляем View.
Таким образом View сама себя обновляет. Ни Model ни App никак не воздействуют на View. В ABAP программах View не может обновиться без взаимодействия пользователя. Эту возможность нужно использовать для ослабления связей View.
В обработчике события DOUBLE_CLICK настраиваем проваливание в MM03 при двойном клике на MATNR.
Как можно заметить выше, проваливание в MM03 реализовано в самой View, хоть это можно отнести и к бизнес логике. На уровень View данная логика вынесена исходя из соображений: к модели не требуется обращаться за дополнительными данными; логика относится только к ALV, т.е. если бы была реализация View в виде Excel или PDF, то данное событие невозможно было бы обработать.
Аналогичным способом можно обработать сложную View с подэкранами и полями ввода/вывода. В таком случае PBO и PAI модули будут вызывать соответствующие методы View. При необходимости View будет отправлять события контроллеру.
Спасибо за информацию буду знать
Очень любопытная статья. Я достаточно долго программирую на ABAP. Но такие паттерны не применял. Да я вывожу базовый select с критериями в класс. Но вот дальше. Зачем на простой вывод отчета городить аж 3 класса? Не понимаю. Ведь можно в базе написать метод show_alv например и он выведет ваш отчет. Написать там же метод print_preview и он выведет на печать. Если чуть сложная задача надо редактировать сохранять добавлять обновлять записи и т д. Делаются методы save update save_as … Как бы можно обойтись 1 классом. Тоесть достаточно только модели. И практически все будет находиться в одном классе.
Преимущества данного паттерна можно увидеть разработках со сложной экранной и бизнес-логикой. Простой отчет приведен как пример для понимания принципа паттерна.
Да, но в примере совсем простая логика которая в реале не требует 3 класса на простейший отчет.
Если усложнить задачу. Скажем так. Есть выборка несколько записей. Кликаем по выборке. Открывается диалоговое окно. С каким то полем из этой записи. По нажатию кнопки сохранить, сохраняем запись, обновляем в гриде.
Было бы неплохо увидеть такую статью.. посложнее.
Спасибо.
Вопрос из разряда «Зачем писать программу на 10 строк, чтобы она вывела Hello World, если можно в блокноте просто написать Hello World». Такой вопрос интересовал меня в школе, на первом уроке по программированию.
На мой взгляд, даже в простых отчетах имеет смысл использовать MVC (MVA), т.к. всё простое очень часто становится сложным, а переписывать потом никто не хочет и просто наваливает свое «простое» сверху. Типа: «мне надо то всего одну выгрузку в Ексель добавить», «а мне просто добавить редактирование поле», «а мне проверочку»….
Автор хорошая статья, особенно для староверов, которые привыкли все делать в подпрограммах. Давно использую MVC и всегда задают один вопрос, зачем городить столько классов на простую программу. Ответ, любая «популярная» программа имеет свойство быстро разрастаться и его сопровождение превращается в ад.