После окончания университета я несколько лет работал программистом C#. Я разрабатывал приложения на WPF с использованием шаблона проектирования MVVM. Затем перешел на ABAP. К большому удивлению обнаружил что ABAP является скорее процедурным языком чем объектно-ориентированным, хотя SAP прилагает большие усилия для продвижения ОО-парадигмы. Для разделения бизнес-логики от GUI как правило используют архитектурный шаблон MVC. Пытаясь реализовать MVC шаблон я каждый раз сталкивался с определенными сложностями, которые делают поддержку программы еще более сложной чем если бы она была написана на процедурах. Не смотря на то, что реализация MVC подробно и с примерами описана в книге Design Patterns in ABAP Objects и на специализированных ресурсах (sapland.ru, blogs.sap.com и др.), проблемы с разделением логики остаются. В реализации MVC на ABAP независимой частью остается Model, а View и Controller тесно связаны между собой. Сильное сопряжение между View и Controller затрудняет поддержку и масштабируемость. Ниже описано почему так происходит и что с этим делать.
Подробно описывать принцип работы шаблонов MVC и MVVM в данной статье я не буду. Приведу лишь основные моменты, которые понадобятся нам в дальнейшем.
Основное отличие MVC от MVVM в том, что в первой Controller знает как View, так и Model, также допускается что View будет знать о Model.
![image](https://habrastorage.org/webt/cu/_2/1g/cu_21gearrjyfbqan1j1p8znjnw.png)
В MVVM шаблоне связь между слоями более слабая. View знает только ViewModel, а ViewModel только Model. View получает данные от ViewModel через ссылку на DataContex.
![image](https://habrastorage.org/webt/iq/td/w3/iqtdw3s_2-wkq7t7ymjqfzvsyr0.png)
Шаблон MVVM предназначен для разработки в WPF на языке C#. Но его идею можно применять и в ABAP.
При реализации MVC, как правило, классы Model выносят в глобальное определение, а классы View и Controller в локальное. Использование локальных классов обусловлено необходимостью взаимодействия с GUI элементами. Дело в том, что на ABAP-классах нельзя построить полноценный GUI. В классах View можно использовать функционал для формирования GUI (CL_SALV_TABLE, REUSE_ALV_GRID_DISPLAY и т.п.), но этого не достаточно. Создать GUI-статусы, заголовки, экраны, PBO, PAI в классе невозможно.
Локальные View и Controller, имеют ряд недостатков:
Желая использовать преимущества MVVM в ABAP и сделать слои более независимыми я определил для себя следующий шаблон разработки.
![image](https://habrastorage.org/webt/xx/-_/vi/xx-_vi85j3x-gyxeja0mkk1vn4s.png)
Так как в чистом виде MVVM реализовать на ABAP нельзя, то ViewModel использовать не совсем корректно. Поэтому вместо ViewModel и Controller я использую Application.
Принцип разделения логики аналогичен принципу MVVM. View передает команды пользователя в Application, а Application воздействует на модель. Обратная связь при этом отсутствует.
Особенностью ABAP приложений является то, то представление может обновиться только после действий пользователя. Даже если какой-нибудь асинхронный процесс поменяет модель, то инициировать обновление представление он не сможет. Данная особенность позволяет ослабить связь модель-представление и делегировать функцию обновления представления самому представлению. Иными словами, представление само должно решать, когда надо обновить себя, а когда нет.
Реализация MVA основана на объектно-ориентированном подходе, где на каждый слой архитектуры будет реализован один или несколько классов. Каждый из слоев обладает рядом свойств.
Представление (View и IView):
Приложение (Application):
Модель (Model):
Рассмотрим реализацию MVA на примере отчета по движению материалов. В качестве взаимодействия с моделью будет кнопка обновления данных.
Диаграмма классов будет выглядеть следующим образом.
![](https://habrastorage.org/webt/it/s3/rg/its3rghr_veuap4swmbbld0e4sq.png)
Model. Конструктор модели будет принимать на входи критерии выбора данных. Класс будет иметь методы инициализации и обновления данных, а также атрибут с данными для отображения.
![](https://habrastorage.org/webt/x4/cy/2o/x4cy2o0aiywqxca8chxe3oiv7_s.png)
![](https://habrastorage.org/webt/lz/yn/k0/lzynk0rxapgzv0tsiukceylkomc.png)
![](https://habrastorage.org/webt/nj/im/fb/njimfbmkojbsmv4gwwsgxekatsk.png)
IView. Интерфейс представления содержит методы установки контекста, отображения представления, события и определение типов контекста.
![](https://habrastorage.org/webt/84/kw/ut/84kwutkhoral94h9xoh419sx2um.png)
![](https://habrastorage.org/webt/tk/oz/wa/tkozwajo-dl9s6_20zg5nzzznuq.png)
![](https://habrastorage.org/webt/hu/7u/rj/hu7urjtj9moqtqmwpbxtgbieut8.png)
IView содержит в себе описание структуры контекста, причем поля структуры должны быть ссылочными
![](https://habrastorage.org/webt/xz/wa/25/xzwa25ffocjyi_ozvjty-ch9qsy.png)
View. Представление реализует интерфейс IView. Причем все события пользователя регистрирует класс View и вызывает только те события, которые нужно обработать приложению. В параметрах событий необходимо передать все данные, которые нужны от View (например, выделенные строки ALV).
Реализация класса View в ALV представлении
![](https://habrastorage.org/webt/pq/sx/mq/pqsxmq6ddx7zb11xzn6atxwsmdm.png)
В данной реализации нам необходимо определение GUI статуса, для этого создадим ФМ и свяжем его с экземпляром CL_SALV_TABLE
![](https://habrastorage.org/webt/tu/yt/at/tuytatkswxr119qopzonuhb5qiw.png)
Важно что все UI события принимает View (в данном случае через ON_USER_COMMAND) и при необходимости View делает RAISE EVENT для Application. Этот подход делает View и Application более независимыми.
Application. Конструктор приложения принимает на вход критерии приложения (параметры экрана выбора) и создает экземпляры Model и View, подписывается на события View и связывает контекст View с Model. Конструктор — это единственное место где приложение знает о View. Application содержит метод RUN, который запускает программу. Запуск приложения можно сравнить с запуском транзакции с заранее определенными параметрами экрана. Это позволяет использовать ее из других программ без SUBMIT.
![](https://habrastorage.org/webt/ka/u_/yh/kau_yh7zytzxbh5eqwj_81-dsxk.png)
Запуск Application. Теперь делаем программу, которая будет запускать приложение.
![](https://habrastorage.org/webt/g8/r-/ss/g8r-sswnvamcb2iuhkmfn1mkvcs.png)
Все, приложение готово. Можно смотреть результат.
![](https://habrastorage.org/webt/zp/fp/he/zpfphe95a8lgwt_i0opqidu-w_o.png)
Бизнес-логика на стороне View. Обработку событий, которые не требуют обращения к модели и контроллеру, можно делать в самой View.
Например, если требуется реализовать открытие MM03 при двойном клике на MATNR, то обработку данной логики можно сделать на стороне View.
![](https://habrastorage.org/webt/ne/cg/_2/necg_25gehx3sq9lclw7wruxvly.png)
На уровень View данная логика вынесена исходя из соображений: к модели не требуется обращаться за дополнительными данными; логика относится только к ALV, т.е. если бы была реализация View в виде Excel или PDF, то данное событие невозможно было бы обработать.
Литература
Design Patterns in ABAP Objects
Паттерны для новичков: MVC vs MVP vs MVVM
Архитектурные шаблоны в ABAP: MVC, MVP, MVVM, MVA
Шаблоны MVC и MVVM
Подробно описывать принцип работы шаблонов MVC и MVVM в данной статье я не буду. Приведу лишь основные моменты, которые понадобятся нам в дальнейшем.
Основное отличие MVC от MVVM в том, что в первой Controller знает как View, так и Model, также допускается что View будет знать о Model.
![image](https://habrastorage.org/webt/cu/_2/1g/cu_21gearrjyfbqan1j1p8znjnw.png)
В MVVM шаблоне связь между слоями более слабая. View знает только ViewModel, а ViewModel только Model. View получает данные от ViewModel через ссылку на DataContex.
![image](https://habrastorage.org/webt/iq/td/w3/iqtdw3s_2-wkq7t7ymjqfzvsyr0.png)
Шаблон MVVM предназначен для разработки в WPF на языке C#. Но его идею можно применять и в 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.
MVVM в ABAP или MVA
Желая использовать преимущества MVVM в ABAP и сделать слои более независимыми я определил для себя следующий шаблон разработки.
![image](https://habrastorage.org/webt/xx/-_/vi/xx-_vi85j3x-gyxeja0mkk1vn4s.png)
Так как в чистом виде 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
Рассмотрим реализацию MVA на примере отчета по движению материалов. В качестве взаимодействия с моделью будет кнопка обновления данных.
Диаграмма классов будет выглядеть следующим образом.
![](https://habrastorage.org/webt/it/s3/rg/its3rghr_veuap4swmbbld0e4sq.png)
Model. Конструктор модели будет принимать на входи критерии выбора данных. Класс будет иметь методы инициализации и обновления данных, а также атрибут с данными для отображения.
![](https://habrastorage.org/webt/x4/cy/2o/x4cy2o0aiywqxca8chxe3oiv7_s.png)
![](https://habrastorage.org/webt/lz/yn/k0/lzynk0rxapgzv0tsiukceylkomc.png)
![](https://habrastorage.org/webt/nj/im/fb/njimfbmkojbsmv4gwwsgxekatsk.png)
IView. Интерфейс представления содержит методы установки контекста, отображения представления, события и определение типов контекста.
![](https://habrastorage.org/webt/84/kw/ut/84kwutkhoral94h9xoh419sx2um.png)
![](https://habrastorage.org/webt/tk/oz/wa/tkozwajo-dl9s6_20zg5nzzznuq.png)
![](https://habrastorage.org/webt/hu/7u/rj/hu7urjtj9moqtqmwpbxtgbieut8.png)
IView содержит в себе описание структуры контекста, причем поля структуры должны быть ссылочными
![](https://habrastorage.org/webt/xz/wa/25/xzwa25ffocjyi_ozvjty-ch9qsy.png)
View. Представление реализует интерфейс IView. Причем все события пользователя регистрирует класс View и вызывает только те события, которые нужно обработать приложению. В параметрах событий необходимо передать все данные, которые нужны от View (например, выделенные строки ALV).
Реализация класса View в ALV представлении
![](https://habrastorage.org/webt/pq/sx/mq/pqsxmq6ddx7zb11xzn6atxwsmdm.png)
В данной реализации нам необходимо определение GUI статуса, для этого создадим ФМ и свяжем его с экземпляром CL_SALV_TABLE
![](https://habrastorage.org/webt/tu/yt/at/tuytatkswxr119qopzonuhb5qiw.png)
Важно что все UI события принимает View (в данном случае через ON_USER_COMMAND) и при необходимости View делает RAISE EVENT для Application. Этот подход делает View и Application более независимыми.
Application. Конструктор приложения принимает на вход критерии приложения (параметры экрана выбора) и создает экземпляры Model и View, подписывается на события View и связывает контекст View с Model. Конструктор — это единственное место где приложение знает о View. Application содержит метод RUN, который запускает программу. Запуск приложения можно сравнить с запуском транзакции с заранее определенными параметрами экрана. Это позволяет использовать ее из других программ без SUBMIT.
![](https://habrastorage.org/webt/ka/u_/yh/kau_yh7zytzxbh5eqwj_81-dsxk.png)
Запуск Application. Теперь делаем программу, которая будет запускать приложение.
![](https://habrastorage.org/webt/g8/r-/ss/g8r-sswnvamcb2iuhkmfn1mkvcs.png)
Все, приложение готово. Можно смотреть результат.
![](https://habrastorage.org/webt/zp/fp/he/zpfphe95a8lgwt_i0opqidu-w_o.png)
Бизнес-логика на стороне View. Обработку событий, которые не требуют обращения к модели и контроллеру, можно делать в самой View.
Например, если требуется реализовать открытие MM03 при двойном клике на MATNR, то обработку данной логики можно сделать на стороне View.
![](https://habrastorage.org/webt/ne/cg/_2/necg_25gehx3sq9lclw7wruxvly.png)
На уровень View данная логика вынесена исходя из соображений: к модели не требуется обращаться за дополнительными данными; логика относится только к ALV, т.е. если бы была реализация View в виде Excel или PDF, то данное событие невозможно было бы обработать.
Литература
Design Patterns in ABAP Objects
Паттерны для новичков: MVC vs MVP vs MVVM
Архитектурные шаблоны в ABAP: MVC, MVP, MVVM, MVA
UnknownUser
Спасибо, довольно интересно, надо будет попробовать.
У меня давно было ощущение того, что попытка слепой реализации MVC в ABAP только усложняет приложение, без какого либо положительного эффекта.