Меню

Архитектурный паттерн MVA или MVVM в ABAP. Реализация

|

Обычно, при смене языка программирования, программисты пытаются применить лучшие практики и шаблоны из языка, на котором они писали ранее. Для отделения логики пользовательского интерфейса от бизнес-логики используют архитектурные паттерны MVC либо его модификации (MVP, MVVM и др.). При наличии опыта работы с такими языками, как Java, C#, C++, PHP, Python и т.п., сложностей с переходом обычно не возникает. Однако, при переходе на ABAP, программисты сталкиваются с рядом проблем, из-за которых многие отказываются от упомянутых паттернов.

Оглавление

Введение

Проблемы MVC в ABAP

Шаблоны MVC и MVVM

Шаблон MVA или MVVM в ABAP

Концепция MVA

Реализация MVA

Модель (Model)

Листинг 1 – Реализация модели (Model)

Интерфейс представления (IView)

Листинг 2 – Реализация интерфейса представления (IView)

Представление (View)

Листинг 3 - Реализация ALV представления (View)

Листинг 4 – Реализация GUI логики View в ALV представлении

Приложение (Application)

Листинг 5 – Реализация приложения (Application)

Листинг 6 – Исходный код главной программы

Бизнес-логика на стороне View

Листинг 6 – Реализация бизнес-логики на стороне View

В статье представлена концепция и реализация шаблона MVA, основанного на паттерне MVVM. Реализация MVC на ABAP описана на sapland.ru, abap4.rublogs.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 имеют ряд недостатков:

  1. View и Controller имеют доступ ко всем глобальным переменным и параметрам экрана выбора.
  2. Обработка 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

Попробовал сделать с TABSTRIP т.е. есть экран селекционный и еще экран вывода. Не получилось. Не ловит событие.

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

Тим Иван

  |  16 августа 2022, 15:46

Попробовал сделать с TABSTRIP т.е. есть экран селекционный и еще экран вывода. Не получилось. Не ловит событие.

И еще один момент что для вывода использовал splitter