Меню

MVC или как писать отчеты быстро и просто

|

Следующие несколько абзацев помогут тебе сэкономить время при разработке большинства видов отчетов на языке ABAP.

Оглавление

Введение

Создание приложения

Model

View

Controller

Модули, активация и первый запуск

Алгоритм работы

Насколько тема актуальна?

Заключение

Введение

Привет, коллега!

Следующие несколько абзацев помогут тебе сэкономить время при разработке большинства видов отчетов на языке ABAP. Так же они помогут тебе разнести всю программную логику по отдельным блокам - классам. Для дальнейшего понимания материала тебе понадобятся всего лишь базовые знания объектно-ориентированного подхода к программированию.

Я уже чувствую, как в твоей голове летает долька сомнения и недоверия, но давай по порядку. Речь пойдет о так называемом шаблоне архитектуры или паттерне MVC (что такое шаблоны архитектуры более подробно можно прочитать здесь). А рассматривать их мы будем на примере создания классического отчета с использованием ALV-таблицы.

Мы разберем самый простой пример - отчет на основе ALV-таблицы, который будет показывать нам все единицы оборудования, созданные после заданной на экране даты.

Создание приложения

Я предлагаю сразу рассматривать архитектуру на живом примере, но для начала давайте посмотрим на рисунок ниже (Рис. 1). На нем схематично изображены составляющие части MVC.

Рис. 1. Схема взаимодействия компонентов MVC

Model, View и Controller (отсюда и MVC) - это те самые компоненты, которые нам необходимо реализовать в виде отдельных классов. Основной идеей такой архитектуры является разделение обработки данных, пользовательского интерфейса и управляющей логики на три отдельных компонента: модель, представление и контроллер. При таком разнесении логики модификация каждого компонента может осуществляться независимо.

Для чего нужен каждый из компонентов:

  • Модель (Model) предоставляет данные и реагирует на команды контроллера, изменяя свое состояние.
  • Представление (View) отвечает за отображение данных модели пользователю, реагируя на изменения модели.
  • Контроллер (Controller) интерпретирует действия пользователя, оповещая модель о необходимости изменений.

За счет разделения у нас появляются более широкие возможности повторного использования кода (наследование). Особенно это помогает, когда пользователь должен видеть те же самые данные в различных контекстах и/или с различных точек зрения. Это наиболее актуально в следующих ситуациях:

  • Когда к одной модели необходимо использовать несколько представлений. Например, когда одни и те же данные требуется вывести вформате ALV, PDF и на Write'ах.
  • Когда необходимо изменить реакцию на действие пользователя на экране. В таком случае достаточно использовать другой контроллер, а представление и модель остаются без изменений.
  • Для разделения и/или ускорения ведения разработки, когда реализация каждого компонента архитектуры достается разным разработчикам.

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

Model

Model (модель) - это некоторый объект, представляющий из себя данные. Например, полученный список единиц оборудования из таблицы EQUI.

Давайте сразу же его и реализуем в нашем приложении (Рис. 2).

Для описания каждого класса я советаю делать свой отдельный инклуд. Итак, создаем новый инклуд в нашей программе и описываем в нем класс lcl_model.

Рис. 2. Список единиц оборудования из таблицы EQUI

Мы только что описали класс, с помощью которого будет происходить получение данных по ЕО для вывода в ALV таблицу. В конструкторе мы запоминаем дату создания ЕО, начиная с которой будет происходить поиск. Далее в методе init_data получаем данные из БД. Эта логика выполнена в виде отдельного метода, потому что он нам еще понадобится в следующем уроке.

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

View

Переходим к реализации класса отображения на экране полученной объектом модели  информации - lcl_view. Так же, как и в предыдущем пункте, создаем для него отдельный инклуд (рис. 3).

Рис. 3. Реализация класса отображения

Конкретно

Если хотите прочитать статью полностью и оставить свои комментарии присоединяйтесь к sapland

У вас уже есть учетная запись?

Войти

Обсуждения Количество комментариев15

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

Олег Башкатов

  |  24 апреля 2017, 01:05

у Вас в разработке по сути 2 view, независимых друг от друга:
1) это селекционный экран отчета
2) и экран ALV на базе класса cl_salv_table
и эти два view контролируются разными controller (1 - создается стандартно за кадром; 2 - создался Вами).
 
кроме того, Вы говорите
"данный шаблон отлично подходит (масштабируется) для отчетов с большим количеством всевозможных enjoysap control'ами с реализацией их взаимодействия.", а при этом используете упрощенный класс cl_salv_table.
почему?

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

Олег Точенюк

  |  24 апреля 2017, 09:48

у Вас в разработке по сути 2 view, независимых друг от друга:
1) это селекционный экран отчета
2) и экран ALV на базе класса cl_salv_table
и эти два view контролируются разными controller (1 - создается стандартно за кадром; 2 - создался Вами).
 
кроме того, Вы говорите
"данный шаблон отлично подходит (масштабируется) для отчетов с большим количеством всевозможных enjoysap control'ами с реализацией их взаимодействия.", а при этом используете упрощенный класс cl_salv_table.
почему?

Ну это для упрощения наверное, чтобы не рисовать много кода. Тут же только принцип организации изложен вроде как.

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

Юрий Жуков

  |  25 апреля 2017, 09:11

Передавать таблицу в display не совсем правильно с точки зрения ООП, вроде. Например, появиться требование вывести кроме таблицы, ещё какую-нибудь заголовочную часть. Потребуется вносить изменения в контроллер, хотя изменилась только структура данных и её отображение, а управление осталось прежним.

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

Евгений Лапшин

  |  25 апреля 2017, 13:07

1. Иван, разве обновление данных не должно происходить через контроллер (рис.1)?
2. Не описана обработка действий пользователя VIEW. Хочется видеть хотя бы самый простой пример.

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

Иван Тюменьев

  |  25 апреля 2017, 15:35

у Вас в разработке по сути 2 view, независимых друг от друга:
1) это селекционный экран отчета
2) и экран ALV на базе класса cl_salv_table
и эти два view контролируются разными controller (1 - создается стандартно за кадром; 2 - создался Вами).
 
кроме того, Вы говорите
"данный шаблон отлично подходит (масштабируется) для отчетов с большим количеством всевозможных enjoysap control'ами с реализацией их взаимодействия.", а при этом используете упрощенный класс cl_salv_table.
почему?

Это первая статья и она вводная. Не хотелось запихивать все и сразу. Пример использования нескольких контролов обязательно будет в дальнейшем.

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

Иван Тюменьев

  |  25 апреля 2017, 15:39

Передавать таблицу в display не совсем правильно с точки зрения ООП, вроде. Например, появиться требование вывести кроме таблицы, ещё какую-нибудь заголовочную часть. Потребуется вносить изменения в контроллер, хотя изменилась только структура данных и её отображение, а управление осталось прежним.

В данном случае передача таблицы в display скорее необходимость. Т.к. в конструктор передать changing параметр невозможно.
 
Вывод не только таблицы будет показан в дальнейшем.
 
"Не совсем правильно с точки зрения ООП" - хотелось бы немного аргументации...

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

Иван Тюменьев

  |  25 апреля 2017, 15:51

1. Иван, разве обновление данных не должно происходить через контроллер (рис.1)?
2. Не описана обработка действий пользователя VIEW. Хочется видеть хотя бы самый простой пример.

1) Существуют различные реализации паттерна MVC. В данном случае смысла в ручном обновлении нет, т.к. связь View с таблицей модели по ссылке. Обновятся данные в таблице - передавать их заново не придется.
2) Все будет, следите за новыми статьями! (;

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

Юрий Жуков

  |  25 апреля 2017, 16:47

В данном случае передача таблицы в display скорее необходимость. Т.к. в конструктор передать changing параметр невозможно.
 
Вывод не только таблицы будет показан в дальнейшем.
 
"Не совсем правильно с точки зрения ООП" - хотелось бы немного аргументации...

Необходимости нет. По-хорошему во view должна быть ссылка на модель и view должна сама брать данные из модели. А из модели уж как угодно можно данные выдавать или как атрибут или через метод (зависит от того насколько надо абстрагироваться). Тогда и контроллер не будет зависеть от структуры модели. В контроллере должна быть только настройка связи между view и model.
 
Не совсем правильно, потому что такая передача нарушает инкапсуляцию внутренностей объекта модель, контроллер "знает" как устроена модель, хотя, по идее, ему должно быть всё равно, что внутри модели. Если конечно придерживаться концепции пассивной модели, то и передача через контроллер выглядит нормальной, так как вся бизнес логика всё равно будет сосредоточена в контроллере. А если делать контроллер по всем правилам, то в нём должна быть только логика управления связями между остальными объектами и маршрутизация сообщений в их обработчики. Для простого ALV отчета, это правда уже перебор.

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

Иван Тюменьев

  |  28 апреля 2017, 14:48

Необходимости нет. По-хорошему во view должна быть ссылка на модель и view должна сама брать данные из модели. А из модели уж как угодно можно данные выдавать или как атрибут или через метод (зависит от того насколько надо абстрагироваться). Тогда и контроллер не будет зависеть от структуры модели. В контроллере должна быть только настройка связи между view и model.
 
Не совсем правильно, потому что такая передача нарушает инкапсуляцию внутренностей объекта модель, контроллер "знает" как устроена модель, хотя, по идее, ему должно быть всё равно, что внутри модели. Если конечно придерживаться концепции пассивной модели, то и передача через контроллер выглядит нормальной, так как вся бизнес логика всё равно будет сосредоточена в контроллере. А если делать контроллер по всем правилам, то в нём должна быть только логика управления связями между остальными объектами и маршрутизация сообщений в их обработчики. Для простого ALV отчета, это правда уже перебор.

Если идти по первому пути, то мы получим высокую связность между объектами: вью будет знать с какой он работает моделью. Передавая данные модели через контроллер, мы убираем эту связь. Только контроллер продолжает знать, с какой моделью он работает. По хорошему, конечно, я должен был получить ссылку на таблицу модели в контроллере и передать ее уже вью. Но я не вижу смысла в таком финте: создавать get метод в модели, запомнить ссылку на таблицу в контроллере, потом ее передать дальше. В случае нескольких моделей это имеет смысл...
На счет нарушения инкапсуляции, согласен. К сожалению, до версии 7.40 очень громосткий код получается, если писать все по канонам. Но опять же, это один из примеров реализации, который каждый может доработать под себя.

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

Олег Точенюк

  |  28 апреля 2017, 23:38

>>"Самое главное - это расположить инклуды с моделью и представлением до инклуда с контроллером"
 
А может проще написать в начале что-то типа:
CLASS: <имя> DEFINITION DEFERRED,
       <имя> DEFINITION DEFERRED,
       <имя> DEFINITION DEFERRED.
 
И тогда будет все равно как оно там дальше в инклудах находится.

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

Олег Башкатов

  |  29 апреля 2017, 14:59

>>"Самое главное - это расположить инклуды с моделью и представлением до инклуда с контроллером"
 
А может проще написать в начале что-то типа:
CLASS: <имя> DEFINITION DEFERRED,
       <имя> DEFINITION DEFERRED,
       <имя> DEFINITION DEFERRED.
 
И тогда будет все равно как оно там дальше в инклудах находится.

если на то пошло, то можно использовать OOP подход при создании транзакции, и забыть про include как таковой и про их расположение в том числе :-)
 
пример описан здесь:
sapland.ru/blogs/phaizullin

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

Юрий Жуков

  |  05 мая 2017, 09:23

Если идти по первому пути, то мы получим высокую связность между объектами: вью будет знать с какой он работает моделью. Передавая данные модели через контроллер, мы убираем эту связь. Только контроллер продолжает знать, с какой моделью он работает. По хорошему, конечно, я должен был получить ссылку на таблицу модели в контроллере и передать ее уже вью. Но я не вижу смысла в таком финте: создавать get метод в модели, запомнить ссылку на таблицу в контроллере, потом ее передать дальше. В случае нескольких моделей это имеет смысл...
На счет нарушения инкапсуляции, согласен. К сожалению, до версии 7.40 очень громосткий код получается, если писать все по канонам. Но опять же, это один из примеров реализации, который каждый может доработать под себя.

>> вью будет знать с какой он работает
>>моделью. Передавая данные модели через
>>контроллер, мы убираем эту связь.
 
Вью не будет знать с какой моделью оно работает, оно будет знать только что объект который ей передали как модель реализует интерфейс, который необходим для вью. А вот передавая данные средствами контроллера как раз таки и создается высокая связность. Появляется как минимум на одну связь больше - если меняется интерфейс модели, приходится менять и котроллер.

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

Рамиль Тен

  |  16 июня 2017, 13:17

если на то пошло, то можно использовать OOP подход при создании транзакции, и забыть про include как таковой и про их расположение в том числе :-)
 
пример описан здесь:
sapland.ru/blogs/phaizullin

Не понятно зачем реализовать данный функционал в глобальном классе. Это как то излишне. Кроме того сомнительно использование концепции статьи в больших разработках.
Легче реализовать функционал по отрисовке ALV и отработке событий в одном классе. А в конструктор передавать контейнер и набор входных данных.

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

Михаил Короченков

  |  11 октября 2017, 15:04

Иван, вы c первой статьи учите не правильному стилю MVC (в ABAP-е в частности) скорее всего потому что сами только пробуете данный подход и не набили на нем руку достаточно.
А именно: представления (View) как я думаю вы знаете бывают тонкие и толстые (которые лишь отображают данные и  которые кроме отображения производят над ними действия). При использовании патерна MVC в ABAPe используется почти всегда концепция толстого представления, даже ваше простое view является таковым. Исходя из такого подхода обязательно нужно хранить данные выводимые(обрабатываемые) представлением в самом представлении (хранить в атрибутах,конечно же нужно их туда передавать(как именно разговор другой). Только так достигается независимость View от Model( для примера с ALV-view очень часто нужна структура данных во вью отличающееся от модели, при этом ссылка никакая вам не поможет, да и ссылка это тоже не корректный подход толстого представления).

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

Михаил Короченков

  |  11 октября 2017, 15:05

Иван, вы c первой статьи учите не правильному стилю MVC (в ABAP-е в частности) скорее всего потому что сами только пробуете данный подход и не набили на нем руку достаточно.
А именно: представления (View) как я думаю вы знаете бывают тонкие и толстые (которые лишь отображают данные и  которые кроме отображения производят над ними действия). При использовании патерна MVC в ABAPe используется почти всегда концепция толстого представления, даже ваше простое view является таковым. Исходя из такого подхода обязательно нужно хранить данные выводимые(обрабатываемые) представлением в самом представлении (хранить в атрибутах,конечно же нужно их туда передавать(как именно разговор другой). Только так достигается независимость View от Model( для примера с ALV-view очень часто нужна структура данных во вью отличающееся от модели, при этом ссылка никакая вам не поможет, да и ссылка это тоже не корректный подход толстого представления).