Реализация MVVM в ABAP

После окончания университета я несколько лет работал программистом 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 в данной статье я не буду. Приведу лишь основные моменты, которые понадобятся нам в дальнейшем.

Основное отличие MVC от MVVM в том, что в первой Controller знает как View, так и Model, также допускается что View будет знать о Model.

image

В MVVM шаблоне связь между слоями более слабая. View знает только ViewModel, а ViewModel только Model. View получает данные от ViewModel через ссылку на DataContex.

image

Шаблон 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, имеют ряд недостатков:

  1. View и Controller имеют доступ ко всем глобальным переменным и параметрам экрана выбора.
  2. Обработка PBO и PAI в Controller требует получения состояния View (например получение выделенных строк ALV) или обновление View (например обновление таблицы ALV). В качестве решения данной проблемы нередко можно увидеть публичные атрибуты View, на которые воздействует Controller, или когда View имеет ссылку на Controller. Оба решения плохие, т.к. в первом случае нарушается инкапсуляция, а во втором Low Coupling.

MVVM в ABAP или MVA


Желая использовать преимущества MVVM в ABAP и сделать слои более независимыми я определил для себя следующий шаблон разработки.

image

Так как в чистом виде 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 на примере отчета по движению материалов. В качестве взаимодействия с моделью будет кнопка обновления данных.

Диаграмма классов будет выглядеть следующим образом.



Model. Конструктор модели будет принимать на входи критерии выбора данных. Класс будет иметь методы инициализации и обновления данных, а также атрибут с данными для отображения.







IView. Интерфейс представления содержит методы установки контекста, отображения представления, события и определение типов контекста.







IView содержит в себе описание структуры контекста, причем поля структуры должны быть ссылочными



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

Реализация класса View в ALV представлении



В данной реализации нам необходимо определение GUI статуса, для этого создадим ФМ и свяжем его с экземпляром CL_SALV_TABLE



Важно что все UI события принимает View (в данном случае через ON_USER_COMMAND) и при необходимости View делает RAISE EVENT для Application. Этот подход делает View и Application более независимыми.

Application. Конструктор приложения принимает на вход критерии приложения (параметры экрана выбора) и создает экземпляры Model и View, подписывается на события View и связывает контекст View с Model. Конструктор — это единственное место где приложение знает о View. Application содержит метод RUN, который запускает программу. Запуск приложения можно сравнить с запуском транзакции с заранее определенными параметрами экрана. Это позволяет использовать ее из других программ без SUBMIT.



Запуск Application. Теперь делаем программу, которая будет запускать приложение.



Все, приложение готово. Можно смотреть результат.



Бизнес-логика на стороне View. Обработку событий, которые не требуют обращения к модели и контроллеру, можно делать в самой View.

Например, если требуется реализовать открытие MM03 при двойном клике на MATNR, то обработку данной логики можно сделать на стороне View.



На уровень View данная логика вынесена исходя из соображений: к модели не требуется обращаться за дополнительными данными; логика относится только к ALV, т.е. если бы была реализация View в виде Excel или PDF, то данное событие невозможно было бы обработать.

Литература

Design Patterns in ABAP Objects
Паттерны для новичков: MVC vs MVP vs MVVM
Архитектурные шаблоны в ABAP: MVC, MVP, MVVM, MVA
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 19

    0

    Спасибо, довольно интересно, надо будет попробовать.
    У меня давно было ощущение того, что попытка слепой реализации MVC в ABAP только усложняет приложение, без какого либо положительного эффекта.

      0
      Попробуйте. Будет интересно услышать обратную связь после применения.
        0
        Попытался сделать какой нибудь простенький пример.
        Пока вызывает дискомфорт то, что в интерфейсе view жёстко прописана структура контекста.
        Пока не понимаю, как можно сделать его более абстрактным.
        Хотя, может, я слишком многого хочу от ABAP'а )).
          0
          В чем дискомфорт и зачем ее абстрагировать? Удобно же когда информация для отображения аккумулирована в одном месте.
            0
            Я бы предпочёл чтобы был вообще один общий интерфейс для View, а не свой собственный на каждый отдельный отчёт. В таком случае получится реально очень гибкие «кирпичики».
            По идее, это можно решить, путём использования ссылок на DATA в классах вместо структур и таблиц. Опять таки, побочный эффект решения — проблемы с читабельностью такой структуры.
              0
              Один View на все Z-отчеты в системе? Мне кажется это не реально.
      0

      Уважаемый автор, а зачем нужны все эти дополнительные сущности, по сравнению с базовым вариантом написать напрямую весь код в тексте report в se38?
      Вроде никакого повторного использования кода ваш вариант не предполагает..

        0
        Для разделения логики приложения от логики пользовательского интерфейса.
          +1
          Постараюсь высказаться яснее.
          Логику приложения отделяют от логики пользовательского интерфейса не просто потому что кто-то сказал, что «это делать хорошо», а для достижения каких-то задач.

          Например, это может быть задача упростить дальнейшую поддержку и модернизацию, особенно если этим будет заниматься не тот разработчик, который изначально писал программу.
          Я утверждаю, что при таком мотиве гораздо лучше использовать общепринятый подход, т.е. просто выделить в отдельные perform код для выборки данных, вывода ALV и реакции на user-command. Ваша же схема потребует от нового разработчика долго разбираться, какой же там мега-шаблон вы использовали и не дает никаких преимуществ для целей поддержки.

          Может быть другая задача — сделать больше одного пользовательского интерфейса для одной и той же функциональности. Например, обычный SAP GIU под Windows и какое-нибудь мобильное приложение. Вот в этом случае усилия по полной изоляции логики интерфейса от логики работы с данными оправданы. Но часто ли у вас встречается такая задача?

            0
            Архитектурные паттерны не следует применять во всех задачах подряд. Если у вас простой ALV отчет, то использовать данный подход не имеет смысла. А если есть экраны, подэкраны и дополнительный набор функций, то отсутствие архитектуры может свести к тому, что программа станет не поддерживаемой через некоторое время.

            Что касается вашего комментария:
            это может быть задача упростить дальнейшую поддержку и модернизацию

            Да, это упростит дальнейшую поддержку и сделает программу более гибкой к расширению функционала

            Я утверждаю, что при таком мотиве гораздо лучше использовать общепринятый подход, т.е. просто выделить в отдельные perform код для выборки данных, вывода ALV и реакции на user-command

            Ваши утверждения не обоснованы. Вызов PERFORM означает использование процедурного программирование. Это не общепринятый подход, а практически единственно возможный способ программирования в ABAP до появления ООП.

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

            Напротив, поддержка будет более простой и гибкой если знать принцип. А для понимания принципа достаточно прикрепить ссылку на статью в задании на разработку ;)

            Может быть другая задача — сделать больше одного пользовательского интерфейса

            Я в качестве примера приводил когда требуется выводить данные в разных представлениях: ALV, Excel, PDF.
              0
              Думаю, что использование средств ООП в простых отчетах с alv оправданно с точки зрения сокращения времени разработки. Вы можете использовать интерфейсы для определения методов, которые обычно работают в отчете. А для alv-grid можно сделать удобную обертку. А если вы пишете подпрограммы вы неизбежно себя повторяете и тратите кучу времени в рутинных задачах
          0
          Согласен в том, что в abap сложно полноценно реализовать mvc, но такой вариант реализации MVA, на мой взгляд, сложен. Хотя иногда приходиться делать что то подобное. Полностью тут я вряд ли опишу свой подход, задам несколько вопросов:
          1. Почему бы модель не сделать обработчиком событий view-ера? Так как у события есть sender, модель сможет влиять на view и получать данные о его состоянии.
          2. Что плохого в локальных классах, если речь идёт о типовом отчете с селекционным экраном? У меня обычно три класса — application, model, view. Для приложения я использую интерфейс, viewer обычно наследует класс-обертку над cl_gui_alv_grid. Если вариантов представления несколько, есть класс для каждого view.
          3. Я раньше тоже в каждом application делал конструктор с параметрами, это слишком долго и плохо поддерживается. Это ещё и мешает прийти к такой архитектуре, которую можно использовать повторно и получать выгоду в скорости
            0
            1. Вы предлагаете модели подписываться на события вьювера? Тогда для чего нужен контроллер?
            2. Локальные классы имеют доступ к глобальным переменным. Также GUI элементы необходимо будет создавать в основной программе что делает невозможным использовать их в других разработках. Если View наследовать от ALV, то View и Controller будут сильно сопряжены.
            3. Если в конструктор передавать структуру с критериями, то такие разработки очень легко поддерживаются. Допустим, расширился экран выбора на несколько параметров. Потребуется расширить структуру CRITERIA и все. Остальная логика не будет затронута.
            Данная архитектура проверена временем как на простых отчетах, так и на программах со сложной экранной логикой.
              0
              1. Есть разные события у того же грида. Одни работают исключительно с данными, влияют на них и их обработку стоит делать в модели, а есть события, которым не нужен доступ к данным, их можно обрабатывать в контроллере. Вообще можно сказать, что cl_gui_alv_grid уже является контроллером.
              2. Не так страшны глобальные переменные как использование их или их аналогов. В моих отчетах единственные глобальные переменные это параметры селекционного экрана, доступ к которым осуществляется внутри класса application, в других они практически ни к чему.
              3. В чем ещё плох подход application-а с конструктором? В том что у селекционного экрана куча событий, которые нужно обрабатывать в более менее серьезном отчете. У экранов обычных событий не так много, но все же они есть, и их тоже кто то должен обрабатывать.

              Создаётся впечатление, что вы заложник архитектуры и используете ее только ради использования архитектуры.

                0
                > В чем ещё плох подход application-а с конструктором? В том что у селекционного экрана куча событий, которые нужно обрабатывать в более менее серьезном отчете. У экранов обычных событий не так много, но все же они есть, и их тоже кто то должен обрабатывать.
                Да, у селекционника может быть много событий. А в чем проблема?
                  0
                  Проблемы, может быть и нет. Просто интересно, как вы обрабатываете, например, события INITIALIZATION, AT SELECTION-SCREEN OUTPUT, AT SELECTION-SCREEN, если они будут зависеть от данных, введенных на селекционном экране. По идее, эти события должны обрабатываться классом приложения, но экземпляр этого класса вы создаете только в START-OF-SELECTION.
                    0
                    Эти события реализую непосредственно в коде основной программы. В Application передаю уже выбранные параметры.
                      0
                      Т.е. получается, что:
                      1. класс Application не полностью реализует функционал приложения, что, на мой взгляд, странно. Интересно, как вы будете строить архитектуру решения, если вам потребуется сделать alv grid на селекционном экране для выбора, например классов классификации, а в отчете будет еще два alv grid-а;
                      2. скорее всего, ни один такой отчет вы не используете дважды и каждый раз пишете много одинакового или однотипного кода, т.е. во времени разработки нет выигрыша;
                      0
                      По идее, эту проблему можно решить, создавая APPLICATION в LOAD-OF-PROGRAM.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое