Архитектурный паттерн MVA или MVVM в ABAP. Реализация
Обычно, при смене языка программирования, программисты пытаются применить лучшие практики и шаблоны из языка, на котором они писали ранее. Для отделения логики пользовательского интерфейса от бизнес-логики используют архитектурные паттерны MVC либо его модификации (MVP, MVVM и др.). При наличии опыта работы с такими языками, как Java, C#, C++, PHP, Python и т.п., сложностей с переходом обычно не возникает. Однако, при переходе на ABAP, программисты сталкиваются с рядом проблем, из-за которых многие отказываются от упомянутых паттернов.
Оглавление
Листинг 1 – Реализация модели (Model)
Интерфейс представления (IView)
Листинг 2 – Реализация интерфейса представления (IView)
Листинг 3 - Реализация ALV представления (View)
Листинг 4 – Реализация GUI логики View в ALV представлении
Листинг 5 – Реализация приложения (Application)
Листинг 6 – Исходный код главной программы
Листинг 6 – Реализация бизнес-логики на стороне View
В статье представлена концепция и реализация шаблона MVA, основанного на паттерне MVVM. Реализация MVC на ABAP описана на sapland.ru, abap4.ru, blogs.sap.com и в книге Design Patterns in ABAP Objects. Если читатель не знаком с паттерном MVC, то перед прочтением рекомендуется ознакомиться с материалами по ссылкам выше.
Введение
Обычно, при смене языка программирования, программисты пытаются применить лучшие практики и шаблоны из языка, на котором они писали ранее. Для отделения логики пользовательского интерфейса от бизнес-логики используют архитектурные паттерны MVC либо его модификации (MVP, MVVM и др.). При наличии опыта работы с такими языками, как Java, C#, C++, PHP, Python и т.п., сложностей с переходом обычно не возникает. Однако, при переходе на ABAP, программисты сталкиваются с рядом проблем, из-за которых многие отказываются от упомянутых паттернов.
Проблемы MVC в ABAP
При реализации MVC, как правило, классы Model выносят в глобальное определение, а классы View и Controller в локальное. Использование локальных классов обусловлено необходимостью взаимодействия с GUI элементами. Дело в том, что на ABAP-классах нельзя построить полноценный GUI.
В классах View можно использовать функционал для формирования GUI (CL_SALV_TABLE, REUSE_ALV_GRID_DISPLAY и т.п.), но этого не достаточно. Создать GUI-статусы, заголовки, экраны, PBO, PAI в классе невозможно.
Локальные View и Controller имеют ряд недостатков:
- View и Controller имеют доступ ко всем глобальным переменным и параметрам экрана выбора.
- Обработка PBO и PAI в Controller требует получения состояния View (например получение выделенных строк ALV) или обновление View (например обновление таблицы ALV). В качестве решения данной проблемы нередко можно увидеть публичные атрибуты View, на которые воздействует Controller, или когда View имеет ссылку на Controller. Оба решения плохие, т.к. в первом случае нарушается инкапсуляция, а во втором Low Coupling.
Шаблоны MVC и MVVM
Подробно описывать принцип работы шаблонов MVC и MVVM в данной статье я не буду. Приведу лишь основные моменты, которые понадобятся нам в дальнейшем.
Основное отличие MVC от MVVM в том, что в первой Controller знает, как View, так и Model, также допускается, что View будет знать о Model. Схематическое различие между MVC и MVVM представлено на Рис.1 и Рис.2.
Рис. 1 – Схема шаблона Model-View-Controller
В MVVM шаблоне связь между слоями более слабая. View знает только ViewModel, а ViewModel только Model. View получает данные от ViewModel через ссылку на DataContex.
Рис. 2 – Схема шаблона Model-View-ViewModel
Шаблон MVVM предназначен для разработки в WPF на языке C#. Но его идею можно применять и в ABAP.
Шаблон MVA или MVVM в ABAP
Желая использовать преимущества MVVM в ABAP и сделать слои более независимыми, я определил для себя следующий шаблон разработки (Рис. 3).
Рис. 3 – Схема шаблона Model-View-Application
Так как в чистом виде MVVM реализовать на ABAP нельзя, то ViewModel использовать не совсем корректно. Поэтому вместо ViewModel и Controller я использую Application.
Принцип разделения логики аналогичен принципу MVVM. View передает команды пользователя в Application, а Application воздействует на модель. Обратная связь при этом отсутствует.
Особенностью ABAP приложений является то, что представление может обновиться только после действий пользователя. Даже если какой-нибудь асинхронный процесс поменяет модель, то инициировать обновление представление он не сможет. Данная особенность позволяет ослабить связь модель-представление и делегировать функцию обновления представления самому представлению. Иными словами, представление само должно решать, когда надо обновить себя, а когда нет.
Концепция MVA
Реализация MVA основана на объектно-ориентированном подходе, где на каждый слой архитектуры будет реализован один или несколько классов. Каждый из слоев обладает рядом свойств.
Представление (View и IView):
- MVA работает с абстракцией представления IView. Все классы View должны содержать реализацию IView.
- IView содержит события, которые требуют взаимодействия с моделью
- IView содержит контекст — ссылка на данные модели, которые необходимо отобразить пользователю
- View может содержать бизнес-логику, которая не требует взаимодействия с моделью. Например, если требуется реализовать из ALV проваливание в карточку контрагента, то данная логика будет относиться к представлению.
- View содержит GUI элементы в группе функций, которая связана с классом View.
Приложение (Application):
- Выполняет роль связки представления и модели и является точкой входа в приложение.
- Имеет критерии запуска — набор параметров, которые определяют с какими параметрами необходимо запустить приложение. Обычно это параметры селекционного экрана.
- Критерии приложения состоят из критериев модели и представления. Например, если на селекционном экране требуется ввести дату проводки и указать флаг вывода отчета PDF или ALV, то дата проводки будет относиться к критериям модели, а флаг PDF и ALV к критериям представления.
- В конструктор приложения передаются критерии запуска. Приложение создает модель и представление, подписывается на события представления, связывает контекст представления с моделью.
Модель (Model):
- Содержит публичные атрибуты, которые необходимы представлению.
- Содержит критерии расчета модели и метод инициализации.
Реализация MVA
В коде я буду придерживаться классических обозначений MVC, за исключением контроллера. Его буду называть Application или App.
В выборе между локальными и глобальными классами предпочтение отдаю последним. Использование глобальных классов позволит отказаться от SUBMIT и CALL TRANSACTION, если потребуется вызывать программу из другой разработки.
Рассмотрим реализацию MVA на примере отчета по движению материалов. Для взаимодействия с моделью будет использоваться кнопка обновления данных.
На Рис. 4 представлена диаграмма классов разрабатываемого отчета.
Рис. 4 – Диаграмма классов демонстрационной программы
Модель (Model)
Поля, необходимые для отображения определим в структуре ZSMVC_DEMO_OUTTAB (Рис. 5).
Рис. 5 – Поля таблицы для отображения пользователю
Модель будет реализована в классе ZCL_MVC_DEMO_MODEL. Конструктор модели будет принимать на входи критерии выбора данных. Класс будет иметь методы инициализации и обновления данных, а также атрибут с данными для отображения.
На Рис 6. и Рис 7. представлены публичные методы и атрибуты модели.
Рис. 6 – Методы Model
Рис. 7 – Атрибуты Model
Листинг 1 – Реализация модели (Model)
CLASS zcl_mvc_demo_model DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. TYPES: BEGIN OF mts_criteria, matnr TYPE RANGE OF mseg-matnr, charg TYPE RANGE OF mseg-charg, budat TYPE RANGE OF mkpf-budat, END OF mts_criteria . DATA mt_matmove TYPE STANDARD TABLE OF zsmvc_demo_outtab WITH DEFAULT KEY. METHODS constructor IMPORTING !is_criteria TYPE mts_criteria . METHODS initialization . METHODS refresh . PROTECTED SECTION. PRIVATE SECTION. DATA ms_criteria TYPE mts_criteria . ENDCLASS. CLASS ZCL_MVC_DEMO_MODEL IMPLEMENTATION. * <SIGNATURE>----------------------------------------------------------------------------------+ * | Instance Public Method ZCL_MVC_DEMO_MODEL->CONSTRUCTOR * +--------------------------------------------------------------------------------------------+ * | [-à] IS_CRITERIA TYPE MTS_CRITERIA * +---------------------------------------------------------------------------------</SIGNATURE> METHOD constructor. ms_criteria = is_criteria. ENDMETHOD. * <SIGNATURE>----------------------------------------------------------------------------------+ * | Instance Public Method ZCL_MVC_DEMO_MODEL->INITIALIZATION * +--------------------------------------------------------------------------------------------+ * +---------------------------------------------------------------------------------</SIGNATURE> METHOD initialization. refresh( ). ENDMETHOD. * <SIGNATURE>----------------------------------------------------------------------------------+ * | Instance Public Method ZCL_MVC_DEMO_MODEL->REFRESH * +--------------------------------------------------------------------------------------------+ * +---------------------------------------------------------------------------------</SIGNATURE> METHOD refresh. CLEAR mt_matmove[]. SELECT mseg~mblnr mseg~mjahr mseg~zeile mkpf~budat mkpf~blart mseg~matnr mseg~charg mseg~menge mseg~meins mseg~dmbtr mseg~waers INTO CORRESPONDING FIELDS OF TABLE mt_matmove FROM mseg JOIN mkpf ON mkpf~mblnr EQ mseg~mblnr AND mkpf~mjahr EQ mseg~mjahr WHERE mseg~matnr IN ms_criteria-matnr AND mseg~charg IN ms_criteria-charg AND mkpf~budat IN ms_criteria-budat. ENDMETHOD. ENDCLASS.
Интерфейс представления (IView)
Интерфейс представления содержит методы установки контекста (Рис. 8), отображения представления, события (Рис. 9) и определение типов контекста (Рис. 10).
Рис. 8 – Методы IView
Рис. 9 – События IView
Рис. 10 – Типы IView
IView содержит в себе описание структуры контекста, причем поля структуры должны быть ссылочными.
Листинг 2 – Реализация интерфейса представления (IView)
INTERFACE zif_mvc_demo_view PUBLIC . TYPES: mtt_outtab TYPE STANDARD TABLE OF zsmvc_demo_outtab WITH DEFAULT KEY . TYPES: BEGIN OF mts_context, outtab TYPE REF TO mtt_outtab, END OF mts_context . DATA ms_context TYPE mts_context . EVENTS refresh . METHODS set_context IMPORTING !is_context TYPE mts_context . METHODS display . ENDINTERFACE.
Представление (View)
Представление реализует интерфейс IView. Причем все события пользователя регистрирует класс View и вызывает только те события, которые нужно обработать приложению. В параметрах событий необходимо передать все данные, которые нужны от View (например, выделенные строки ALV).
Листинг 3 - Реализация ALV представления (View)
CLASS zcl_mvc_demo_view_alv DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. INTERFACES zif_mvc_demo_view . EVENTS refresh . METHODS on_user_command FOR EVENT added_function OF cl_salv_events_table IMPORTING !e_salv_function . PROTECTED SECTION. PRIVATE SECTION. ALIASES ms_context FOR zif_mvc_demo_view~ms_context . DATA mo_salv TYPE REF TO cl_salv_table . ENDCLASS. CLASS zcl_mvc_demo_view_alv IMPLEMENTATION. * <SIGNATURE>----------------------------------------------------------------------------------+ * | Instance Public Method ZCL_MVC_DEMO_VIEW_ALV->ON_USER_COMMAND * +--------------------------------------------------------------------------------------------+ * | [--->] E_SALV_FUNCTION LIKE * +---------------------------------------------------------------------------------</SIGNATURE> METHOD on_user_command. CASE e_salv_function. WHEN 'REFRESH'. RAISE EVENT refresh. mo_salv->refresh( ). WHEN OTHERS. ENDCASE. ENDMETHOD. * <SIGNATURE>----------------------------------------------------------------------------------+ * | Instance Public Method ZCL_MVC_DEMO_VIEW_ALV->ZIF_MVC_DEMO_VIEW~DISPLAY * +--------------------------------------------------------------------------------------------+ * +---------------------------------------------------------------------------------</SIGNATURE> METHOD zif_mvc_demo_view~display. DATA lo_event TYPE REF TO cl_salv_events_table. IF mo_salv IS NOT BOUND. TRY . CALL METHOD cl_salv_table=>factory IMPORTING r_salv_table = mo_salv CHANGING t_table = ms_context-outtab->*[]. CATCH cx_salv_msg INTO DATA(lx_salv_msg). MESSAGE lx_salv_msg TYPE rs_c_error. ENDTRY. lo_event = mo_salv->get_event( ). SET HANDLER on_user_command FOR lo_event. ENDIF. CALL FUNCTION 'Z_MVC_DEMO_VIEW_ALV_DISPLAY' EXPORTING io_salv = mo_salv. ENDMETHOD. * <SIGNATURE>----------------------------------------------------------------------------------+ * | Instance Public Method ZCL_MVC_DEMO_VIEW_ALV->ZIF_MVC_DEMO_VIEW~SET_CONTEXT * +--------------------------------------------------------------------------------------------+ * | [--->] IS_CONTEXT TYPE MTS_CONTEXT * +---------------------------------------------------------------------------------</SIGNATURE> METHOD zif_mvc_demo_view~set_context. IF ms_context IS NOT INITIAL. IF ms_context NE is_context. MESSAGE e674(ilm_stor). " Источник контекста уже установлен, "
Если хотите прочитать статью полностью и оставить свои комментарии присоединяйтесь к sapland
ЗарегистрироватьсяУ вас уже есть учетная запись?
Войти
Обсуждения 2
Комментарий от
Тим Иван
| 16 августа 2022, 15:39
Комментарий от
Тим Иван
| 16 августа 2022, 15:46
Тим Иван 16 августа 2022, 15:39
Попробовал сделать с TABSTRIP т.е. есть экран селекционный и еще экран вывода. Не получилось. Не ловит событие.