Global Data in ABAP OO Programs
I decided to spend a bit of time looking at some of the blogs being published out on SCN as I found myself with a bit of time. Unfortunately I found a lot of examples of poor quality coding, obsolete techniques and non-nonsensical use of OO programming.
As many people are pushing to learn the latest technologies, unfortunately there are many who have not yet grasped many of the fundamental principles of good quality coding. More often than not I have found that having a solid understanding of the syntax and the basic design principles outlined by the good old gang of four has helped me to quickly get up to speed with new advances in technology. There is no point trying to run, before you can walk and eventually it shows in the work you deliver. Testament to this is the WebDynpro developers that embed business logic in the Views of their WebDynpro components or the OO Developers that use classes in the same way they used to use function modules or subroutines.
If you think you are in the above category, then I urge you to go back and learn the basics. There is nothing to be ashamed of. I recently did it myself. Having been persecuted for so many years for trying to use OO techniques that I learnt at university (as it was not fair to other developers who could not code in OO) , I brushed off my old books and gemmed up.
One of the key principles in programming is called the “Separation of Concerns“. So one of the first things that breaks this principle is the use of global data. I’m sure you have come across a piece of code in a massive program in a huge subroutine and had a mental breakdown deciphering what the impact of your change is going to be. Well if you modularise your code, preferably in OO, you have a lot less to worry about.
The time for using global program variables (yes and even in report writing) can and should pretty much come to an end. Below is a little sample report, which I think exemplifies the usage of passing references and minimal use of the global reference.
*& Report ZKP_TEMPLATE_REPORT
*& A very simple MVC template for creating reports.
*& Note the distinct lack of global variables in the main program and
*& even in my classes. It is far better to pass references into methods,
*& then keep track of everything globally.
* CLASS zcl_report_model DEFINITION
* This is a really simplistic model but you could work with your own
* model objects and better incorporate inheritance if required,
* to better encapsulate your business logic
CLASS zcl_report_model DEFINITION.
” Shared type elements that are reused globally in my application
” are placed here in the model
TYPES: ty_carrid TYPE RANGE OF s_carr_id,
ty_currcode TYPE RANGE OF s_currcode,
tty_scarr TYPE STANDARD TABLE OF scarr WITH KEY carrid.
METHODS: get_data IMPORTING im_carrid TYPE zcl_report_model=>ty_carrid
im_currcode TYPE zcl_report_model=>ty_currcode
RETURNING value(re_scarr) TYPE tty_scarr.
” This is the only piece of global data I use in my application
” it exists only as long as I have a reference to an instance of
” this class in my program
DATA: gt_scarr TYPE tty_scarr.
ENDCLASS. “zcl_report_model DEFINITION
* CLASS zcl_report_view DEFINITION
* Your view is really how you intend to present the information. This
* class is abstract and so is the method implying you need to redefine
* it. (Hence no implementation required)
* You may want to render your view in different ways ALV, FILE, EMAIL
CLASS zcl_report_view DEFINITION ABSTRACT.
METHODS: display_data ABSTRACT IMPORTING im_scarr TYPE zcl_report_model=>tty_scarr.
ENDCLASS. “zcl_report_view DEFINITION
* CLASS zcl_report_view_alv DEFINITION
* So here is my new class that will implement the display method
* using an ALV. You can do a lot here with the SALV class for
* displaying data, but this will be very simple.
CLASS zcl_report_view_alv DEFINITION FINAL INHERITING FROM zcl_report_view.
METHODS: display_data REDEFINITION.
* CLASS zcl_report_controller DEFINITION
* This class will be used to handle the flow of the program. Passing
* information from the views and requesting data from the model.
* No business logic should be placed here
CLASS zcl_report_controller DEFINITION ABSTRACT FINAL.
” Import all the report selection variables into the controller method
” for executing the report
IMPORTING im_carrid TYPE zcl_report_model=>ty_carrid
im_currcode TYPE zcl_report_model=>ty_currcode.
ENDCLASS. “zcl_report_controller DEFINITION
” I need a global data statement for my selection option
” I could create a generic select-option by adding the
” name in brackets, however I lose the benefits from the data dictionary
” Search helps etc in my selection screen
” Beyond this I will not use this reference again.
” It’s not ideal but reporting was never going to be 100% OO
DATA: gt_scarr TYPE scarr.
” Replace these with your report variables
SELECT-OPTIONS: s_carrid FOR gt_scarr–carrid,
s_curcod FOR gt_scarr–currcode.
” Now kick off the report logic
zcl_report_controller=>execute_report( im_carrid = s_carrid
im_currcode = s_curcod ).
* CLASS zcl_report_controller IMPLEMENTATION
CLASS zcl_report_controller IMPLEMENTATION.
DATA: lo_model TYPE REF TO zcl_report_model,
lt_scarr TYPE zcl_report_model=>tty_scarr,
lo_disp TYPE REF TO zcl_report_view,
lo_disp_alv TYPE REF TO zcl_report_view_alv.
CREATE OBJECT lo_model.
” Execute the model method to retrieve the data I need for my report
lt_scarr = lo_model->get_data( im_carrid = im_carrid
im_currcode = im_currcode ).
CREATE OBJECT lo_disp_alv.
” Now output the data via the view
” The reason I have assigned the specialised ALV class reference to the
” generalised view class reference is purely overkill in this example.
” However I can very quickly create a new view class and assign this
” perhaps using a case statement to determine which view to output.
lo_disp = lo_disp_alv.
lo_disp->display_data( im_scarr = lt_scarr ).
ENDCLASS. “zcl_report_controller IMPLEMENTATION
* CLASS zcl_report_view IMPLEMENTATION
CLASS zcl_report_view_alv IMPLEMENTATION.
DATA: lo_salv_tab TYPE REF TO cl_salv_table,
lx_salv_msg TYPE REF TO cx_salv_msg,
lt_scarr TYPE zcl_report_model=>tty_scarr.
lt_scarr = im_scarr.
” The SALV classes are excellent for quickly generating output for display
” The factory method here generates a new instance based on my input data.
” I could manipulate the output further prior to display if I need
cl_salv_table=>factory( IMPORTING r_salv_table = lo_salv_tab
CHANGING t_table = lt_scarr ).
CATCH cx_salv_msg INTO lx_salv_msg.
” You should use better exception handling here. Possibly raise it up
” to the controller class and deal with an appropriate action there
MESSAGE e000(zkp) WITH ‘An error occurred rendering the results’(001)
‘as an ALV’(002).
ENDCLASS. “zcl_report_view IMPLEMENTATION
* CLASS zcl_report_model IMPLEMENTATION
CLASS zcl_report_model IMPLEMENTATION.
DATA: lt_scarr TYPE tty_scarr.
IF gt_scarr IS NOT INITIAL.
re_scarr = gt_scarr.
INTO TABLE lt_scarr
WHERE carrid IN im_carrid
AND currcode IN im_currcode.
IF sy–subrc = 0.
re_scarr = gt_scarr = lt_scarr.
ENDCLASS. “zcl_report_model IMPLEMENTATION
At this point I won’t be mentioning singleton classes in the context of global data, but they do have their place. And there is a pretty good blog out there discussing this in ABAP.
I totally agree with your comments regarding global data in reports, function modules etc.
What we have done at my company to facilitate the usage of OO programming even for simple reports was to create a report template in SE80 (available via <CTRL>-F6). The report template we created is quite similar to the one you posted. The main differences are that we use global classes for implementing the report and added predefined some auxiliary classes like logging top SLG1 and displaying of ALV-grids. A developer using the template only needs to implement one class and ends up with a report with a nice ALV output, logging to SLG1 and a clean implementation
in most of the cases anyway).
After rolling out the template to all our development systems the developers basically stopped to create reports without using the template. The reason is that using the template took the initial overhead of OO-based reports (i.e. need to create different development artifacts) away. So this overhead was no excuse anymore. As a result the added benefits of cleaner code became immediately apparent to everyone.
Thanks for the feedback and great point on usage of templates. The majority of the templates I seem to come across are more focused on ensuring you provide the same details as is available in the version history of the code. 😐 It's great to hear they are being put to good use.
Personally I feel the overheads are not that much bigger in OO, as you become more accustomed to developing with it. I should be clear that this is specifically with regard to local classes and admittedly global classes are a fair bit more tedious using the class editor, however the source code editor option in SE24 is great to speed things up. 🙂
i guess this template is not available anymore :)?
cheers & greetings from Ratingen,
Nice blog Katan!
Maybe you forgot to mention that if we design selection-screen, then the elements have to be global. Unfortunately SAP doesn't let you define screens in classes 🙁
So you can't design a 100% MVC-report in SAP. What are your thoughts?
Thanks Suhas. I kind of inferred in my comments in the code. But you are absolutely right here, its important to be explicit.
Thanks for the template. I'm not strongly against using the Local Classes as You can't leverage them again. I believe as you have already written the code, you want to reuse that if same requirement pops up in future.
I also faced similar issues from fellow developers as part of my QA reviews - Like Using Static methods, using Subroutines in the methods etc. I have posted many of them at ABAP Object Oriented Approach for Reports – Initial Design on my personal site - http://zevolving.com/. In the second
To fix many of them and leverage MVC, I have been using the Model, View and Controller. Additionally I also use Selection Screen object to make it independent to the selection screen. You can find the second part at ABAP Object Oriented Approach for Reports – Redesign.
Thanks for the feedback.
I am familiar with your work and I think its great you keep pushing the boundaries to improve peoples understanding and the uptake of OO concepts. I think you have some great examples of design patterns on your site.
I think I shot myself in the foot here using the term "template" and I am going to remove that to avoid further confusion. My main focus here was on the usage of global variables in application development. The different ways this can be achieved is a much bigger topic and I think your website covers off on a lot of that. Also I think Thorsten Franz wrote some good stuff as well..
That is quite a neat example.
I tend to use a bit different approach using OO though. What you have coded in the controller I usually code inside start-of-selection. In my opinion, the same rule from the selection screen applies: it's not possible to be 100% OO anyways so adding a class to encapsule the execution behind report execution is not a big benefit. Of course, using the start-of-selection as a controller forces me to use global objects references but I guarantee that for reports is not a big deal. Actually, it's pretty useful when you can insert some validation logic from classes in events other than the start-of-selection.
Regarding the output structure (specially when it's an ALV or other table format), I tend to declare an interface containing the line type of the output as well a table type for it. Then I will have a couple of classes with selection logic and then a separate class to put all data together with an implementation of the output type interface. It's a great technique if you want to do some unit testing without worry about data selection.
Thanks for the post.
Why not blog about your approach to an OO ABAP report? How do you implement MVC in your reports? And how do you Unit test them?
I can see quite a few blogs in the series. 🙂
Yes, definitely I should. I don't consider my approach MVC but very suitable for reports. I just wanted to compare my logic with yours using MVC concepts.
Unfortunately my last works use standard tables that are not available on all systems (most of them exists due to activation of business functions) so I will have to create an example using other standard tables (particularly I'd rather show this example not using the flight model).
Thanks for the idea. I'll definitely let you know in case of a blog post.
Let me congratulate you on a such a Nice blog regarding MVC development for Reports. Much is said in SCN on ABAP Objects but only a few worth while examples like yourself & other experienced contributors.
I remember once i was assigned to a MNC project & there they had a similar framework instead there the Selection screen & Display (View ) were both coded in global classes.
So my suggestion is that we can Create a global class which can serve as a global template for Selection screen for all reports the class can have static methods for performing Selection Screen Events. Also we can have a Global Class for Display of ALV & othere related activities.
First of all let me say I'm a big OO fan and an enemy of global data. 🙂
Your blog works great as an example, but is not applicable to all cases. Precisely with ALV, (either SALV model or CL_GUI_ALV_GRID class) many times there's the need to have the internal table as global. Examples are the events double_click or data_changed_finished (editable ALVs): most probably you need to access the data table in those methods (and rereading the data using GET_DATA from model class is of course not the correct approach).
I recall also that the CFW methods which don't do an inner flush themselves, need the data to be global / accesible at the time / call point of cl_gui_cfw=>flush, which could be in another method. ALV grid rarely needs a separate flush call, but other controls do.
In those cases I suppose your GET_DATA method could return a reference to its private table GT_SCARR (TYPE REF TO DATA), so that external consumers (e.g. your report controller) can access it with field symbols. But this is almost equivalent to making it public/global, as any code outside the class can change it via the reference. Another possible approach could be to keep a copy of the table passed to the ALV in the controller itself (as a private attribute) and on modifications, update the model from it (SET_DATA), But then you have two copies of the data and the associated sincronization complexity...a third approach is of course making GT_SCARR attribute public, which defeats the purpose of this blog.
Because of this things, or the aforementioned selection screen usage, is that I generally break my rule about not using global data (for the ALV only) and "relax" the OO usage a bit, leaving MVC out. But I'm interested in the approach you (all the readers of this blog not just the creator) use to solve this problems and still use MVC, without getting the program to be overly complex.
This is how I write a simple report. (Taken from my comment here http://scn.sap.com/community/abap/blog/2014/01/09/classical-way-to-abap-oo-style-of-coding#comment-446277 )
DATA main_ref TYPE REF TO main.
CREATE OBJECT main_ref
Within main_ref there will be a reference to another class. Within that class will be my internal table for the ALV. The only global data is main_ref and the parameters. What's important is that the table must be in memory. That's why a local variable can't work.
What does this "another class" do? Is it for data enhancement/processing etc.?
I just mean that within the main class I may have references to all sorts of other classes - global or local - one of which has the attribute which is the alv table.
Ok, but my point is that, if not for the whole program, it has to be global to some of your classes (as an attribute, any of its methods can access it. If the majority of the program consists mostly on instantiating the main class and calling a couple of public methods, the data is still mostly "global"). The blog example using SALV with local internal table is for VERY simple reports. I hope I'm making myself clear.
I just wanted to remark two things:
- Sometimes we can't escape the need to have variables with broader scope than we would like, because of technical limitations (ALV / CFW). See note 178665 for an example.
- The model design from the example works as long as there's no requirement to change data or access it twice. As soon as those requirements appear (almost in any real world program), it gets far more complex than this to keep it fully OO. That's why I wanted to know how do you cope with those things. I just go with the flow on this and whenever I'm using an ALV with this requirements, my internal table is either global or an attribute of a class. I found balance between classic ABAP and full OO by purposely using just static methods (syntax benefits and easier OO future adaptations are still there) in this kind of programs. Which also leaves room for your average ABAPper which doesn't have a clue about OO (sad but true) to still maintain you programs with a bit of extra effort. I go with full blown OO design only when I deem the application complexity really requires it.
Sorry if this is like 3 years late, but maybe some future readers can shed a light on this. I just wanted to quickly comment/ask on the point that you raised regarding global data.
I see the same issue as you do, the above methods still technically access public data of the a class, and use it among other classes. So it's pretty much like global data. But then correct me if I'm wrong, aren't getters and setters methods there for this purpose, you can have the attribute private and friend the classes to access it.
Or maybe have an interface and having all the class implement that specific interface?
Yes, getter and setter methods are the usual approach, but precisely for ALV, data table must be globally accesible in order to be modified by the standard code, for example when sorting. Also, events like double click or hotspot receive just row and column references, so you need the actual data table accesible in event handlers to process them...