Skip to Content

Hello!

This is my first post here on the forum, so i hope it will be useful for some of less experienced developers.

TLDR:

  • You can utilize interfaces to move class functionalities to higher abstraction level
  • You make code cleaner and nicer to look at
  • Interfaces enable you to create mockup objects and increase code testability
  • SOLID approach can be achieved
  • With interfaces you make your class enabled for extension, but closed for modification
  • Methods can use different object of differrent classes without using RTTS and casting unless they implement the same interface.

As i started to learn ABAP and had former experience with other object oriented languages i struggled with one missing feature here  – unability to overload methods.

What’s a method overloading you might ask?

It’s ability to use the same name of method, but with different arguments passed to the methods and with different implementations.

Without this feature, at some point, classes might become too large and it’s hard to keep track on all methods doing similar activities, but having different names.

Interfaces do not provide overloading abilities, yet they can tidy up and streamline your coding by limiting amount of methods with different names but similar functionalities.

In ABAP we have single class inheritance (so each class can have only one parent) and multiple interface implementation ability.

For example, presented above LCL_Child_Class inherits all not private variables, methods, types and constants from LCL_Parent_Class and must implement all functionalities from LINF_Utility and LINF_Saver interfaces.

To explain what’s an interface i will use “not that professional” description- it’s a class-like entity, but it carries no implementation of declared methods, yet it might carry over constants and types and variables. Interface cannot be initialized.

By default all methods of interface have to be implemented – this is general rule enforced by object-oriented programming creed. It’s possible to make interface method not obligatory to implement, but this will not be described in the scope of this blog post.

“Real life” use case.

Let’s imagine we have a program that needs to feed table SFLIGHT with data from various sources:

  • Excel Upload
  • RFC Upload
  • Upload of modified and inserted lines during program runtime.

Of course we could also add ADBC sources and consumption of JSON/XML soruces fetched via HTTP client object to the list, but i just want to show the gist, not describe all possible examples in details.

As this is just a presentation of possibiilties – i didn’t create real working program start to finish.

Declaration of interfaces:

We are going to create two interfaces, but just one is really necessary in this example.

First one is the most important. I named it linf_sflight_career as this is local interface which is implemented by local classes for excel, rfc and local table carriers.

interface linf_sflight_carrier.
    types: tt_sflight type standard table of sflight with default key,
           st_sflight type sorted table of sflight with non-unique key mandt carrid connid,
           ht_sflight type hashed table of sflight with unique key mandt carrid connid fldate.
    methods: "! Returns hashed table SFLIGHT contents
             "! @parameter r_sflight |
             get_hashed_records returning value(r_sflight) type ht_sflight,
             "! Returns sorted table SFLIGHT contents
             "! @parameter r_sflight |
             get_sorted_records returning value(r_sflight) type st_sflight,
             "! Returns standard table SFLIGHT contents
             "! @parameter r_sflight |
             get_standard_records returning value(r_sflight) type tt_sflight.       
endinterface.

Interface carries various table types and three methods to be implemented by the classes of excel, rfc and table carrier.

Next interface will be implemented by class responsible for saving data to the database.

interface linf_sflight_saver.
    constants: "! Table lock types
               begin of lock_types,
                exclusive type enqmode value 'E',
               end of lock_types.
    constants: "! Scopes for table lock
               begin of scope_range,
                _2 type char01 value '2',
               end of scope_range.
    constants: _sflight type tablename value 'SFLIGHT'.                      
    methods: "! Save data from carrier object to SFLIGHT table
             "! @parameter i_carrier | Carrier object
             save_data importing i_carrier type ref to linf_sflight_carrier.
endinterface.

At this point you might ask – why do we need so many classes to fulfill such simple task? Why so many interfaces, when we could achieve similar effect utilizing class inheritance approach or even using single class for all the purposes.

Answer is obvious – SOLID. If you’d like to know more about this approach then please let me know in the comments and i will create another blog post purely about SOLID.

Returning to the topic –  now class presentation:

class lcl_excel_carrier definition.
    public section.
        interfaces: linf_sflight_carrier.
        aliases: tt_sflight for linf_sflight_carrier~tt_sflight,
                 st_sflight for linf_sflight_carrier~st_sflight,
                 ht_sflight for linf_sflight_carrier~ht_sflight,
                 get_hashed_records for linf_sflight_carrier~get_hashed_records,
                 get_sorted_records for linf_sflight_carrier~get_sorted_records,
                 get_standard_records for linf_sflight_carrier~get_standard_records.
    protected section.
    private section.
        data: standard_sflight type tt_sflight,
              sorted_sflight type st_sflight,
              hashed_sflight type ht_sflight.
endclass.
class lcl_excel_carrier implementation.
  method get_hashed_records.
    r_sflight = hashed_sflight.
  endmethod.
  method get_sorted_records.
    r_sflight = sorted_sflight.
  endmethod.
  method get_standard_records.
    r_sflight = standard_sflight.
  endmethod.
endclass.

class lcl_rfc_carrier definition.
    public section.
            interfaces: linf_sflight_carrier.
            aliases: tt_sflight for linf_sflight_carrier~tt_sflight,
                     st_sflight for linf_sflight_carrier~st_sflight,
                     ht_sflight for linf_sflight_carrier~ht_sflight,
                     get_hashed_records for linf_sflight_carrier~get_hashed_records,
                     get_sorted_records for linf_sflight_carrier~get_sorted_records,
                     get_standard_records for linf_sflight_carrier~get_standard_records.
    protected section.
    private section.
        data: standard_sflight type tt_sflight,
              sorted_sflight type st_sflight,
              hashed_sflight type ht_sflight.
endclass.
class lcl_rfc_carrier implementation.
  method get_hashed_records.
    r_sflight = hashed_sflight.
  endmethod.
  method get_sorted_records.
    r_sflight = sorted_sflight.
  endmethod.
  method get_standard_records.
    r_sflight = standard_sflight.
  endmethod.
endclass.

class lcl_table_carrier definition.
    public section.
            interfaces: linf_sflight_carrier.
            aliases: tt_sflight for linf_sflight_carrier~tt_sflight,
                     st_sflight for linf_sflight_carrier~st_sflight,
                     ht_sflight for linf_sflight_carrier~ht_sflight,
                     get_hashed_records for linf_sflight_carrier~get_hashed_records,
                     get_sorted_records for linf_sflight_carrier~get_sorted_records,
                     get_standard_records for linf_sflight_carrier~get_standard_records.
    protected section.
    private section.
        data: standard_sflight type tt_sflight,
              sorted_sflight type st_sflight,
              hashed_sflight type ht_sflight.
endclass.
class lcl_table_carrier implementation.
  method get_hashed_records.
    r_sflight = hashed_sflight.
  endmethod.
  method get_sorted_records.
    r_sflight = sorted_sflight.
  endmethod.
  method get_standard_records.
    r_sflight = standard_sflight.
  endmethod.
endclass.

Classes presented above have same functionality, but fully implemented classes would have some specific methods for each of their carrier purposes (like filtering, retrieving sflight data from raw format excel data et cetera.

All carrier classes implement linf_sflight_carrier – therefore we didn’t have to define methods all over again. I did however add aliases for better code readability

Next class we’re going to create is database saver called lcl_database_saver

class lcl_database_saver definition.
    public section.
        interfaces: linf_sflight_saver.
        aliases: lock_types for linf_sflight_saver~lock_types,
                 scope_range for linf_sflight_saver~scope_range,
                 save_data for linf_sflight_saver~save_data,
                 _sflight for linf_sflight_saver~_sflight.
    protected section.
    private section.
        methods: "! Creates table lock key for database lock
                 "! @parameter i_sflight_ref | Reference to SFLIGHT table line
                 "! @parameter r_varkey | Varkey returned
                 create_varkey importing i_sflight_ref type ref to sflight
                               returning value(r_varkey) type vim_enqkey,
                 "! Locks table using passed varkey
                 "! @parameter i_varkey | Table lock key
                 "! @parameter i_tabname | Table name
                 "! @parameter r_subrc | Information on lock creation. 0 = okay
                 lock_table_line importing i_varkey type vim_enqkey
                                           i_tabname type tablename default _sflight
                                 returning value(r_is_locked) type abap_bool,
                 "! Unlocks locked table line
                 "! @parameter i_varkey | Table lock key
                 "! @parameter i_tabname | Table name
                 unlock_table_line importing i_varkey type vim_enqkey
                                             i_tabname type tablename default _sflight.
endclass.
class lcl_database_saver implementation.
  method save_data.
        loop at i_carrier->get_standard_records( ) reference into data(standard_line).
            data(varkey) = create_varkey( standard_line ).
            if lock_table_line( i_varkey = varkey ).
                modify sflight from standard_line->*.
                unlock_table_line( exporting i_varkey  = varkey ).
            endif.
        endloop.
  endmethod.
  method lock_table_line.
    call function 'ENQUEUE_E_TABLEE'
      exporting
        mode_rstable   = lock_types-exclusive    " Lock mode for table RSTABLE
        tabname        = i_tabname    " 01th enqueue argument
        varkey         = i_varkey    " 02th enqueue argument
        _scope         = scope_range-_2
      exceptions
        foreign_lock   = 1
        system_failure = 2
        others         = 3.
     r_is_locked = xsdbool( sy-subrc = 0 ).
  endmethod.
  method unlock_table_line.
    call function 'DEQUEUE_E_TABLEE'
      exporting
        mode_rstable = lock_types-exclusive    " Lock mode for table RSTABLE
        tabname      = i_tabname    " 01th enqueue argument
        varkey       = i_varkey    " 02th enqueue argument
        _scope       = scope_range-_2.
  endmethod.
  method create_varkey.
    r_varkey = |{ i_sflight_ref->mandt }{ i_sflight_ref->carrid }{ i_sflight_ref->connid }{ i_sflight_ref->fldate }|.
  endmethod.
endclass.

And finally – we run the example:

initialization.
data(excel_carrier) = new lcl_excel_carrier( ).
data(rfc_carrier) = new lcl_rfc_carrier( ).
data(database_saver) = new lcl_database_saver( ).

try.
    database_saver->save_data( i_carrier = excel_carrier ).
catch cx_sy_assign_cast_illegal_cast.
catch cx_sy_assign_cast_unknown_type.
catch cx_sy_assign_cast_error.
endtry.

try.
    database_saver->save_data( i_carrier = rfc_carrier ).
catch cx_sy_assign_cast_illegal_cast.
catch cx_sy_assign_cast_unknown_type.
catch cx_sy_assign_cast_error.
endtry.

As you can see by moving abstraction to an interface level we can assure that literally ANY class correctly implementing interface linf_sflight_carrier can be passed to saver method and be processed correctly.

Another advantage of this approach is ability to create mockup objects in fast and easy way for your unit tests. Testable code – better code.

That’s all for today – i really hope you liked it 🙂

Cheers,
Cezary

 

To report this post you need to login first.

11 Comments

You must be Logged on to comment or reply to a post.

  1. Michelle Crapo

    Very nice! The code is easy to understand. That’s great because I hate to use hard to read code. The next person looking it after me tends to not like it. (I use it when I have to)

    First blog? I hope it is the first of many.

    Thank you!

    Michelle

    (2) 
  2. Paul Hardy

    I had to summarise what interfaces were the other day, as an abstract concept as opposed to the way they work in ABAP. I came up with the following:-

    • An interface describes a set of functionality that a class promises to provide. That is a set of public methods.
    • An interface contains no code, just public methods and data declarations.
    • Any class that implements that interface must fufill the promise of the interface i.e. have code for each of those public methods.
    • A class may implement several interfaces, this is the ABAP equivalent of multiple inheritance.
    • Whereas a subclass relates to the parent class using a IS-A relationship (e.g. a duck IS-A bird) interfaces use a “HAS-A” relationship as in “class XYZ HAS-A method to send emails”
    • A common OO principal is to favour composition over inheritance i.e. use interfaces instead of subclassing where possible.
    • A real world example would be someone who is an accountant by day and a volunteer firefighter in their spare time. The fire brigade only cares about their fire fighting abilities, and the employer only cares about their accounting abilities.
    • In the programming world a class variable can be typed as an interface, and then any class at all that implements that interface can be passed in. The calling program can only access the methods of the interface, and knows nothing about how many other types of behavior that class is capable of.

    Note that the most important thing to me is that the interface describes to the outside world what functionality you can expect as the calling program does not know or care what concrete class is actually going to be used to do the work, only that it is able to do the work.

    Thus during “injection” the importing parameter is typed as the interface rather than to any type of concrete class. The same deal goes for factory methods. Moreover the ABAP Mocking Framework works off interfaces also.

    Just like with the Liskov Substitution program, where any subclass can do the job of its parent without the caller being any the wiser, whatever actual class that implements the interface is chosen, the calling program can call a method and knows the job will be done.

    Given that, the idea of optional interface methods seems rather bizarre.

     

    (4) 
  3. Łukasz Pęgiel

    None of my blog posts here were so detailed, so it’s really nice to see the work you’ve done Cezary. Hope you’ll not stop (like me at the moment), as sharing and explaining is this is one of the best ways to learn.

    Keep on going!

     

    (3) 
  4. nabheet madan

    Wow very well written. It actually gives more clarity to me about the role of interfaces and how important they are. Will be really happy if you can blog about SOLID via ABAP code as an example.

    (1) 
  5. Cezary Kakowski Post author

    Thank you a lot for the feedback!

    It’s nice to see that such great people from SAP Community encourage me to keep on my work!

    This is really a nice kickstart so i will do my best to keep my blog useful for the greater good – SAP Community 🙂

    (2) 
    1. Cezary Kakowski Post author

      Hi Jacques!

      UML for ABAP? Count me in, this is amazing!

      Frankly some developers i talked to neglect impact of UML diagrams in ABAP development and – this i leave without a comment – they sometimes neglect keeping technical documentation of their code.

      Changing undocumented code is really taking a highway to hell, so it’s good to see that somebody cares for keeping documentation and code clear!

      Cheers,

      Cezary

      (0) 
  6. Suhas Saha

    Hi Cezary,

    I have nothing more to add here, other forum members have already echoed my sentiments.

    But being the critical person that i am, i must ask if you had considered having a “Factory” to deliver the caller the relevant instance to work with?

    BR, Suhas

     

    (1) 
    1. Cezary Kakowski Post author

      Suhas thanks a lot for the comment!

      To be honest – i did consider using factory here. But i had one issue with that – i tried to explain interface concept as simply as possible.

      I believe it’s better to present concept by concept and then connect them instead of putting too much abstract concepts at the same time and trying to explain all at once.

      At least this approach worked for me – little slices at the time and we’re going to eat entire huge steak at ease! 🙂

      Cheers,

      Cezary

       

       

      (0) 

Leave a Reply