MVC или как писать отчеты быстро и просто
Следующие несколько абзацев помогут тебе сэкономить время при разработке большинства видов отчетов на языке ABAP.
Оглавление
Модули, активация и первый запуск
Введение
Привет, коллега!
Следующие несколько абзацев помогут тебе сэкономить время при разработке большинства видов отчетов на языке 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
1) это селекционный экран отчета
2) и экран ALV на базе класса cl_salv_table
и эти два view контролируются разными controller (1 - создается стандартно за кадром; 2 - создался Вами).
кроме того, Вы говорите
"данный шаблон отлично подходит (масштабируется) для отчетов с большим количеством всевозможных enjoysap control'ами с реализацией их взаимодействия.", а при этом используете упрощенный класс cl_salv_table.
почему?
Комментарий от
Олег Точенюк
| 24 апреля 2017, 09:48
Олег Башкатов 24 апреля 2017, 01:05
у Вас в разработке по сути 2 view, независимых друг от друга:
1) это селекционный экран отчета
2) и экран ALV на базе класса cl_salv_table
и эти два view контролируются разными controller (1 - создается стандартно за кадром; 2 - создался Вами).
кроме того, Вы говорите
"данный шаблон отлично подходит (масштабируется) для отчетов с большим количеством всевозможных enjoysap control'ами с реализацией их взаимодействия.", а при этом используете упрощенный класс cl_salv_table.
почему?
Комментарий от
Юрий Жуков
| 25 апреля 2017, 09:11
Комментарий от
Евгений Лапшин
| 25 апреля 2017, 13:07
2. Не описана обработка действий пользователя VIEW. Хочется видеть хотя бы самый простой пример.
Комментарий от
Иван Тюменьев
| 25 апреля 2017, 15:35
Олег Башкатов 24 апреля 2017, 01:05
у Вас в разработке по сути 2 view, независимых друг от друга:
1) это селекционный экран отчета
2) и экран ALV на базе класса cl_salv_table
и эти два view контролируются разными controller (1 - создается стандартно за кадром; 2 - создался Вами).
кроме того, Вы говорите
"данный шаблон отлично подходит (масштабируется) для отчетов с большим количеством всевозможных enjoysap control'ами с реализацией их взаимодействия.", а при этом используете упрощенный класс cl_salv_table.
почему?
Комментарий от
Иван Тюменьев
| 25 апреля 2017, 15:39
Юрий Жуков 25 апреля 2017, 09:11
Передавать таблицу в display не совсем правильно с точки зрения ООП, вроде. Например, появиться требование вывести кроме таблицы, ещё какую-нибудь заголовочную часть. Потребуется вносить изменения в контроллер, хотя изменилась только структура данных и её отображение, а управление осталось прежним.
Вывод не только таблицы будет показан в дальнейшем.
"Не совсем правильно с точки зрения ООП" - хотелось бы немного аргументации...
Комментарий от
Иван Тюменьев
| 25 апреля 2017, 15:51
Евгений Лапшин 25 апреля 2017, 13:07
1. Иван, разве обновление данных не должно происходить через контроллер (рис.1)?
2. Не описана обработка действий пользователя VIEW. Хочется видеть хотя бы самый простой пример.
2) Все будет, следите за новыми статьями! (;
Комментарий от
Юрий Жуков
| 25 апреля 2017, 16:47
Иван Тюменьев 25 апреля 2017, 15:39
В данном случае передача таблицы в display скорее необходимость. Т.к. в конструктор передать changing параметр невозможно.
Вывод не только таблицы будет показан в дальнейшем.
"Не совсем правильно с точки зрения ООП" - хотелось бы немного аргументации...
Не совсем правильно, потому что такая передача нарушает инкапсуляцию внутренностей объекта модель, контроллер "знает" как устроена модель, хотя, по идее, ему должно быть всё равно, что внутри модели. Если конечно придерживаться концепции пассивной модели, то и передача через контроллер выглядит нормальной, так как вся бизнес логика всё равно будет сосредоточена в контроллере. А если делать контроллер по всем правилам, то в нём должна быть только логика управления связями между остальными объектами и маршрутизация сообщений в их обработчики. Для простого ALV отчета, это правда уже перебор.
Комментарий от
Иван Тюменьев
| 28 апреля 2017, 14:48
Юрий Жуков 25 апреля 2017, 16:47
Необходимости нет. По-хорошему во view должна быть ссылка на модель и view должна сама брать данные из модели. А из модели уж как угодно можно данные выдавать или как атрибут или через метод (зависит от того насколько надо абстрагироваться). Тогда и контроллер не будет зависеть от структуры модели. В контроллере должна быть только настройка связи между view и model.
Не совсем правильно, потому что такая передача нарушает инкапсуляцию внутренностей объекта модель, контроллер "знает" как устроена модель, хотя, по идее, ему должно быть всё равно, что внутри модели. Если конечно придерживаться концепции пассивной модели, то и передача через контроллер выглядит нормальной, так как вся бизнес логика всё равно будет сосредоточена в контроллере. А если делать контроллер по всем правилам, то в нём должна быть только логика управления связями между остальными объектами и маршрутизация сообщений в их обработчики. Для простого ALV отчета, это правда уже перебор.
На счет нарушения инкапсуляции, согласен. К сожалению, до версии 7.40 очень громосткий код получается, если писать все по канонам. Но опять же, это один из примеров реализации, который каждый может доработать под себя.
Комментарий от
Олег Точенюк
| 28 апреля 2017, 23:38
А может проще написать в начале что-то типа:
CLASS: <имя> DEFINITION DEFERRED,
<имя> DEFINITION DEFERRED,
<имя> DEFINITION DEFERRED.
И тогда будет все равно как оно там дальше в инклудах находится.
Комментарий от
Олег Башкатов
| 29 апреля 2017, 14:59
Олег Точенюк 28 апреля 2017, 23:38
>>"Самое главное - это расположить инклуды с моделью и представлением до инклуда с контроллером"
А может проще написать в начале что-то типа:
CLASS: <имя> DEFINITION DEFERRED,
<имя> DEFINITION DEFERRED,
<имя> DEFINITION DEFERRED.
И тогда будет все равно как оно там дальше в инклудах находится.
пример описан здесь:
sapland.ru/blogs/phaizullin
Комментарий от
Юрий Жуков
| 05 мая 2017, 09:23
Иван Тюменьев 28 апреля 2017, 14:48
Если идти по первому пути, то мы получим высокую связность между объектами: вью будет знать с какой он работает моделью. Передавая данные модели через контроллер, мы убираем эту связь. Только контроллер продолжает знать, с какой моделью он работает. По хорошему, конечно, я должен был получить ссылку на таблицу модели в контроллере и передать ее уже вью. Но я не вижу смысла в таком финте: создавать get метод в модели, запомнить ссылку на таблицу в контроллере, потом ее передать дальше. В случае нескольких моделей это имеет смысл...
На счет нарушения инкапсуляции, согласен. К сожалению, до версии 7.40 очень громосткий код получается, если писать все по канонам. Но опять же, это один из примеров реализации, который каждый может доработать под себя.
>>моделью. Передавая данные модели через
>>контроллер, мы убираем эту связь.
Вью не будет знать с какой моделью оно работает, оно будет знать только что объект который ей передали как модель реализует интерфейс, который необходим для вью. А вот передавая данные средствами контроллера как раз таки и создается высокая связность. Появляется как минимум на одну связь больше - если меняется интерфейс модели, приходится менять и котроллер.
Комментарий от
Рамиль Тен
| 16 июня 2017, 13:17
Олег Башкатов 29 апреля 2017, 14:59
если на то пошло, то можно использовать OOP подход при создании транзакции, и забыть про include как таковой и про их расположение в том числе :-)
пример описан здесь:
sapland.ru/blogs/phaizullin
Легче реализовать функционал по отрисовке ALV и отработке событий в одном классе. А в конструктор передавать контейнер и набор входных данных.
Комментарий от
Михаил Короченков
| 11 октября 2017, 15:04
А именно: представления (View) как я думаю вы знаете бывают тонкие и толстые (которые лишь отображают данные и которые кроме отображения производят над ними действия). При использовании патерна MVC в ABAPe используется почти всегда концепция толстого представления, даже ваше простое view является таковым. Исходя из такого подхода обязательно нужно хранить данные выводимые(обрабатываемые) представлением в самом представлении (хранить в атрибутах,конечно же нужно их туда передавать(как именно разговор другой). Только так достигается независимость View от Model( для примера с ALV-view очень часто нужна структура данных во вью отличающееся от модели, при этом ссылка никакая вам не поможет, да и ссылка это тоже не корректный подход толстого представления).
Комментарий от
Михаил Короченков
| 11 октября 2017, 15:05
А именно: представления (View) как я думаю вы знаете бывают тонкие и толстые (которые лишь отображают данные и которые кроме отображения производят над ними действия). При использовании патерна MVC в ABAPe используется почти всегда концепция толстого представления, даже ваше простое view является таковым. Исходя из такого подхода обязательно нужно хранить данные выводимые(обрабатываемые) представлением в самом представлении (хранить в атрибутах,конечно же нужно их туда передавать(как именно разговор другой). Только так достигается независимость View от Model( для примера с ALV-view очень часто нужна структура данных во вью отличающееся от модели, при этом ссылка никакая вам не поможет, да и ссылка это тоже не корректный подход толстого представления).