Skip to Content
Technical Articles
Author's profile photo Markus Haug

ABAP OO – 3 Simple ways to start using it in your next project

Have you ever asked yourself how you can integrate the object-oriented approach into your next ABAP project?

Being a technology trainer at SAP, customers often ask me how they can use ABAP OO in their next project without creating complex object models and spending hours modeling their application.

Quite often the use case is not so complex that the traditional procedural approach seems more suitable for implementing the application.

This blog post will show you three simple ways and advantages of how you can integrate ABAP OO in your next project!

 

1. Keep it simple – Bundle your business logic into local classes

Integrating the object-oriented approach into your next ABAP report can be very simple by defining a local class and then implementing the entire business logic within reusable methods.

All you have to do is start by defining a local class with a static method. This will be your entry point after the START-OF-SELECTION event. A simple trick lets you use selection parameters and pass them to the class.

Once you have processed your application data, you can display it using the simple ALV.

In addition to this, you can also take it some steps further by implementing unit tests for critical methods. Unit Tests are a great way to make your application more robust and spin another safety net.

Report z_demo_app.

CLASS lcl_main DEFINITION.

  PUBLIC SECTION.
    CLASS-DATA: gt_spfli TYPE STANDARD TABLE OF spfli.

    CLASS-METHODS:
      "! Entry Point
      "! @parameter iv_carrid | Corresponding importing parameter for the selection parameter
      start IMPORTING iv_carrid TYPE spfli-carrid.
  PROTECTED SECTION.
  PRIVATE SECTION.
    CLASS-METHODS:
      "! Fetches application data from DB
      select_data,

      "! Performs calculation on the application data
      calculate.

ENDCLASS.

CLASS lcl_main IMPLEMENTATION.

  METHOD start.
    " This is your entry point.
    " Start implementing your business logic here

    select_data( ).
  ENDMETHOD.

  METHOD calculate.
    " Perform calculations
  ENDMETHOD.

  METHOD select_data.
    " Select your data

    " Call method
    calculate( ).
  ENDMETHOD.

ENDCLASS.

** Data definition **)
DATA: go_alv TYPE REF TO cl_salv_table.

** Parameters **)
PARAMETERS: pa_car TYPE spfli-carrid.

START-OF-SELECTION.

  " Call static main method and pass through selection parameters
  lcl_main=>start( iv_carrid = pa_car ).

  " Create SALV with static table
  cl_salv_table=>factory(
    IMPORTING
      r_salv_table   = go_alv
    CHANGING
      t_table        = lcl_main=>gt_spfli
  ).

  " Display ALV
  go_alv->display( ).

 

 

2. Create global classes for system-wide reuse and encapsulation

In many projects, you have to create more complex applications, for instance,
applications with various screens. In this case, the above approach can not be applied.

Apart from that, you will definitely have projects where you have to reuse your coding in different applications. Following the conventional procedural programming approach, you would probably create function pools in order to implement your logic.

This same approach can be transferred to the object-oriented way. In addition, the numerous advantages of ABAP OO are free of charge (unit tests, etc.).

 

3. TDD (Test-Driven-Development)

Honestly, this is not the simplest approach to start with. The test-driven development is rather a programming philosophy that requires to be applied in your team.

Compared to the first way, you have to think about your test cases for your application before starting to implement your business logic.

Suppose you need to create a method that calculates a critical part of your application. This method should be implemented and delivered as robust as possible. To achieve this, you first need to think about how you can validate that the method calculates the expected output for a given input.

Let me demonstrate this with a simplified example.

 CLASS-METHODS:
      "! Performs calculation on the application data
      calculate IMPORTING iv_num1 TYPE i iv_num2 TYPE i RETURNING VALUE(rv_result) TYPE i.

The static method should calculate the sum of two integer variables.

Based on this, we can make some expectations for the result:

  • 1 + 1 = 2

This will be our first test case, so let’s define a test method for it.

CLASS ltlc_app DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    METHODS:
      for_1_and_1_return_2 FOR TESTING.
ENDCLASS.

Next, we can implement this test case as follows. At this point, we divide our method into three parts – Given, When & Then.

  METHOD for_1_and_1_return_2.
    " Given
    DATA: lo_cut    TYPE REF TO lcl_main, " CUT = Class under test
          lv_result TYPE i.

    CONSTANTS: lc_exp TYPE i VALUE 2. " Expected output

    " Create object
    lo_cut = NEW #( ).

    " When
    lv_result = lo_cut->calculate(
               iv_num1 = 1
               iv_num2 = 1
             ).

    " Then
    cl_abap_unit_assert=>assert_equals(
      EXPORTING
        act                  = lv_result
        exp                  = lc_exp
        msg                  = 'Your specific error message'
    ).

  ENDMETHOD.

 

Next, run your Unit Test.

Failed%20Unit%20Test

Since we have not yet implemented the method, the unit test has failed. Finally, you can implement the method for the specific use case.

METHOD calculate.
  rv_result = 2.
ENDMETHOD.

It is obviously totally wrong. However, this is not the final solution.

Let’s assume you have implemented a more complex method and you run your unit test after you believe it has been implemented correctly.

 

Unit%20Test

From an abstract point of view, the method seems to return the correct value for the given parameters. The unit test also tells you that it was successful.

To avoid such a situation in a more complex environment, the best practice is to create additional test cases before implementing your method:

  • 1 + 1 = 2
  • 2 + 2 = 4
  • 9 + 3 = 12

This will ensure that your application is more robust. You can also add negative tests.

So let us implement it correctly:

METHOD calculate.
  rv_result = iv_num1 + iv_num2.
ENDMETHOD.

 

Summary

However, the three basic techniques mentioned above are a good way to start using ABAP-OO in your upcoming projects.

To get started, just copy the code snippets into your IDE and start playing around with them to get more familiar with them.

If you are hungry to learn more about TDD, I can recommend the following free openSAP course:

If you are a beginner in this field, I would also like to encourage you to book a place in one of my courses:

Assigned Tags

      15 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jan Braendgaard Petersen
      Jan Braendgaard Petersen

      Very good write-up, thank you. I especially liked the part on TDD with the very simple introduction.

      Author's profile photo Markus Haug
      Markus Haug
      Blog Post Author

      Hi Jan,

      thank you. I am glad to hear this!

      Author's profile photo Matthew Billingham
      Matthew Billingham

      Just a few, possibly unrelated comments.

      1. I commend the encouragement to use OO
      2. How do you pass select options? If I'm using local classes, I let the local classes - or at least lcl_main - access the select options and parameters directly. Or I define a range type and use that in the signature.
      3. Why does your sample code using the old RECEIVING syntax, instead of the functional approach? Certainly from 7.31 it's fine to use the functional approach. I'd also prefer to see NEW than CREATE OBJECT - but as I'm still developing in a 7.31 system occasionally, I can understand keeping that.
      4. Why are you using prefixes such as l_ and lo_ when these are contrary to DSUG and SAP's own recommendations. And against the Clean Code initiative.
      5. gt_spfli should be fully specified for preference. E.g. with DEFAULT KEY. Furthermore, for safety, shouldn't it me READ-ONLY?  Maybe this is just my practice - I only expose attributes read only.
      6. Love the TDD!

      These are minor quibbles, btw. Overall - good stuff. Thanks.

      Author's profile photo Markus Haug
      Markus Haug
      Blog Post Author

      Hi Matthew,

      thanks for your comment!

      2. indeed, it is possible to access the parameters directly. But please keep in mind that this is not the best approach if you want to write testable code. In this case I recommend to pass through the parameters or select options. For select options you can define a range table in your class, and then you can refer your importing or changing parameter to the range type.

      3. I also encourage and enable everyone in my classes to use the new syntax whenever the opportunity arises and whenever it makes sense. In the above example, this would of course make sense. I will update it. Soon there will be a special blog post about the new syntax. So stay tuned!

      4. In many materials and example codes the prefixes are still in use. Especially in many course materials. I use them for consistency reasons. I suppose you have your own internal guidelines.

      5. good point. Yes, for complete encapsulation the read-only approach would be better.

      6. thank you! I have also planned a special post on this topic

      Author's profile photo Matthew Billingham
      Matthew Billingham

      So you’ll use the new syntax whenever you can, but not prefixes… The old syntax is still in use, shouldn’t you stick with that for consistency. ?

      (I know, I'm being very picky... )

      Author's profile photo Paul Hardy
      Paul Hardy

      I would also applaud any attempt to get people to use OO. ABAP Objects came out in 1999 and it is my hope that one day it may be the norm in ABAP programming.

      I regard to the best way to get SELECT-OPTONS (totally non OO things) into classes. I have been struggling with that. If you split your report into INCLUDES (e.g. one TOP for the data, one for class definitions, one for class implentations) then referring to a select-option or parameter directly cause a (false) syntax error i.e. the syntax check gives an error, activation does not.

      I have been declaring a local class to store the selection-screen variables and just after START-OF-SELECTION passing the values into an instance which the other classes can read.

      I saw an OO program the other day which used that function module which reads all the selection screen values off the screen and then dynamically passed the values to a structure which mirrored what was on the selection-screen. Whichever way you do it you have to declare everything twice and keep them in synch unless you don't use INCLUDES and use direct references to the global variables on the selection-screen.

      We are really trying to fit a round peg into a square hole here.The SELECTION-SCREEN concept was designed for procedural programs and so does not play well with OO.

      Cheersy Cheers

      Paul

      Author's profile photo Michael Keller
      Michael Keller

      I've tried once something like this to represent the selection criteria as a class. But I have to admit that in some reports I handle every single selection criteria per IMPORTING parameter over to the class constructor. Certainly not the best idea...

      Author's profile photo Markus Haug
      Markus Haug
      Blog Post Author

      Also a cool way of doing it. Especially the dynamic fetching of the parameters.

      Author's profile photo Markus Haug
      Markus Haug
      Blog Post Author

      The comparison with the round peg brings it to the point.  Select options and parameters were not designed for the object-oriented programming approach. Nevertheless, there are plenty of exciting workarounds out there.

      Your way of outsourcing them in a local class is also interesting. Thanks for sharing!

      Author's profile photo Sergiu Iatco
      Sergiu Iatco

      I found also global classes useful to incapsulate code for outputs. You keep the code outside of output and can test it as report and in output to focus on data presentation. Regards. Sergiu.

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Just like others, I’m all for encouragement to use OO ABAP. But from my personal experience, I would suggest a slightly different path.

      I think starting with global classes is the best option. Most people would start with static methods because they work similarly to function modules. I really like functional calls that make the code look like plain English and this usually doesn’t take a lot of convincing to try in SE24 (which I still find superior to Eclipse ADT, especially for beginners).

      After one gets comfortable with static methods (and probably exception handling as part of it), they’d try to apply them in the scenarios where we actually have an object, e.g. a material or a sales order. Some initial mistakes can be made there, which is fine. But any sensible developer will quickly realize that with static method approach we end up repeating lots of stuff, and that could be a “light-bulb moment” to switch to “proper” class.

      Because global classes are reusable, it makes it easier to see their benefit. E.g. you invest more time in the initial development but then you get to use it in different places. And it quickly becomes apparent that classes/methods are actually easier to use than FMs, so that usually marks the point of no return.

      Only after working up some appetite with global objects, I’d go for a report with a local class. As Paul correctly noted, selection screen concept does not lend itself easily to OO development, by design. So when you start writing such report, the very first step of essentially duplicating all selection screen variables can feel like doing something very stupid. To be honest, in a simplest ALV scenario there are practically no advantages in using OOP vs procedural, thus making it a hard-sell. But if there is any business logic to apply then the benefits of OO approach (like better exception handling, shorter / more readable code, etc.) will be more clear.

      This creates sort of a natural learning pattern where you gradually discover advantages from your own work. I know some folks would disagree but personally, I wouldn’t bring up TDD / Unit Test or patterns up to this point because this can easily spook the more conservative developers. And IMHO it’s more important to get everyone into “the function module rehab” first. The rest can be introduced gradually, in baby steps.

      But everyone learns differently, of course, so there is not a single “one size fits all” solution here.

      Author's profile photo Patrick Van Nierop
      Patrick Van Nierop

      We’ve created a global report-with-alv class that contains most events from a standard report (think start-of-selection,..) as well as the required methods for alv handling (display, build field-catalog,..). A new report uses/contains a local class that inherits from the global one, and in most cases the rest of the development is limited to the creation of a ddic structure and a sql statement.

      Advantages? A handful developers all write the same code. Which makes it simple, fast and most of all easy to maintain.

      Special requirements? Just redefine the relevant methods. And again, when maintaining the code, it suffices to check the local class and you see where it differs from the global one..

      And IMHO, only global methods isn’t OO. But, I believe that in this case each of us has to find his/her own way to the ‘aha-erlebnis’. And to get there it’s keep trying and learn from doing.

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      To be clear, I don't advocate for "only global methods", that wouldn't make any sense. It's just IMHO it's easier to see advantages of global classes than local ones, so they can be a better starting point.

      A generic ALV report template is a great option because, as you said, many (most?) developers need to write those and it involves some code that is pretty much boilerplate.

      Author's profile photo Sergiu Iatco
      Sergiu Iatco

      Report OO with select-options and capabilities to display or get data in table.

      In class you can define all types and reuse as required without redefinition over and over.

      Define the class in SE24 and you can reuse data when needed for other complex report.

      *&---------------------------------------------------------------------*
      *& Purpose - Object Oriented Implementation for a report with select-opotins
      *& Author  - Sergiu Iatco | 2023.03.24
      *& URL     - https://blogs.sap.com/2020/12/09/abap-oo-3-simple-ways-to-start-using-it-in-your-next-project/
      *&---------------------------------------------------------------------*
      REPORT MARA_OO_SELECT_OPTIONS.
      
      CLASS LCL_MARA DEFINITION.
        PUBLIC SECTION.
          TYPES:
                 TY_MATNR TYPE MATNR,
                 TY_MTART TYPE MTART,
      
      *           TY_R_MATNR TYPE RANGE OF MATNR,
      *           TY_R_MTART TYPE RANGE OF MTART,
      
                 TY_R_MATNR TYPE RANGE OF TY_MATNR,
                 TY_R_MTART TYPE RANGE OF TY_MTART,
      
                 TY_T_MARA TYPE STANDARD TABLE OF MARA.
          DATA:  C_T_DATA TYPE STANDARD TABLE OF MARA READ-ONLY.
      
          CLASS-DATA: C_SEL_MATNR TYPE TY_MATNR,
                      c_SEL_MTART TYPE TY_MTART.
      
          METHODS:
            CONSTRUCTOR IMPORTING C_R_MATNR TYPE TY_R_MATNR OPTIONAL
                                  C_R_MTART TYPE TY_R_MTART,
      
            GET_DATA EXPORTING M_T_DATA TYPE TY_T_MARA,
            DISPLAY_DATA.
        PRIVATE SECTION.
          DATA: M_R_MATNR TYPE TY_R_MATNR,
                M_R_MTART TYPE TY_R_MTART.
      
      ENDCLASS.
      
      CLASS LCL_MARA IMPLEMENTATION.
        METHOD CONSTRUCTOR.
          M_R_MATNR[] = C_R_MATNR[].
          M_R_MTART[] = C_R_MTART[].
        ENDMETHOD.
      
        METHOD GET_DATA.
          SELECT * FROM MARA
            INTO TABLE M_T_DATA
            WHERE MATNR IN M_R_MATNR AND
                  MTART IN M_R_MTART.
      
      
          C_T_DATA = M_T_DATA.
        ENDMETHOD.
      
        METHOD DISPLAY_DATA.
          CL_DEMO_OUTPUT=>DISPLAY( C_T_DATA ).
        ENDMETHOD.
      ENDCLASS.
      
      TABLES: MARA.
      *DATA: SEL_MATNR TYPE LCL_MARA=>TY_MATNR. "TYPE from class
      *DATA: SEL_MTART TYPE LCL_MARA=>TY_MTART. "TYPE from class
      
      *DATA: T_DATA TYPE STANDARD TABLE OF MARA.
      DATA: T_DATA TYPE LCL_MARA=>TY_T_MARA. "TYPE from class
      
      *TYPES TY_R_MATNR TYPE RANGE OF MARA-MATNR.
      *TYPES TY_R_MTART TYPE RANGE OF MARA-MTART.
      
      *DATA: R_MATNR TYPE TY_R_MATNR.
      *DATA: R_MTART TYPE TY_R_MTART.
      
      DATA: R_MATNR TYPE LCL_MARA=>TY_R_MATNR. "TYPE from class
      DATA: R_MTART TYPE LCL_MARA=>TY_R_MTART. "TYPE from class
      
      SELECT-OPTIONS:
      *  S_MATNR FOR MARA-MATNR,
      *  S_MTART FOR MARA-MTART.
      
      *  S_MATNR FOR SEL_MATNR,
      *  S_MTART FOR SEL_MTART.
      
        S_MATNR FOR LCL_MARA=>C_SEL_MATNR, "CLASS-DATA
        S_MTART FOR LCL_MARA=>C_SEL_MTART. "CLASS-DATA
      
      START-OF-SELECTION.
       R_MATNR[] = S_MATNR[].
       R_MTART[] = S_MTART[].
      
      DATA: LO_MARA TYPE REF TO LCL_MARA.
        LO_MARA = NEW LCL_MARA( C_R_MATNR = R_MATNR
                                C_R_MTART = R_MTART ).
      *  LO_MARA = NEW LCL_MARA( C_R_MTART = R_MTART ). "optional C_R_MATNR
        LO_MARA->GET_DATA( ).
      * It would help a lot of works if standard SAP reports provided get data
        T_DATA[] = LO_MARA->C_T_DATA[]. "get data and use for other requirements
      *  BREAK-POINT.
        LO_MARA->DISPLAY_DATA( ).

       

      REPORT demo_sel_screen_select_options.
      
      CLASS start DEFINITION.
        PUBLIC SECTION.
          CLASS-DATA name(80) TYPE c.
          CLASS-METHODS main.
      ENDCLASS.
      
      SELECTION-SCREEN BEGIN OF SCREEN 100.
        PARAMETERS: dbtab  TYPE c LENGTH 30 DEFAULT 'SFLIGHT',
                    column TYPE c LENGTH 30 DEFAULT 'CARRID'.
      SELECTION-SCREEN END OF SCREEN 100.
      
      SELECTION-SCREEN BEGIN OF SCREEN 500 AS WINDOW.
        SELECT-OPTIONS selcrit FOR (start=>name).
      SELECTION-SCREEN END OF SCREEN 500.
      
      START-OF-SELECTION.
        start=>main( ).
      
      CLASS start IMPLEMENTATION.
        METHOD main.
          CALL SELECTION-SCREEN 100 STARTING AT 10 10.
          IF sy-subrc <> 0.
            RETURN.
          ENDIF.
          name = dbtab && '-' && column.
          CALL SELECTION-SCREEN 500 STARTING AT 10 10.
          IF sy-subrc <> 0.
            RETURN.
          ENDIF.
          cl_abap_demo_services=>list_table( selcrit[] ).
        ENDMETHOD.
      ENDCLASS.
      

      Enjoy!

      Sergiu

       

       

      Author's profile photo Sergiu Iatco
      Sergiu Iatco

      Standard report SE38: AW01N (Asset Explorer) written with forms and classes.