
OO Design - Method Interface
What should an interface for an ABAP method actually look like, and what advantages do the different designs offer? In this article, we'll look at parameters, structures, and objects for mapping the interface.
Table of contents
In this article, we'll look at the design and use of method interfaces and what they should ideally look like today.
Introduction
Nowadays, ABAP code should primarily be defined in classes. You can follow various patterns to adhere to best practices and ensure testability. However, there are also further best practices for defining and designing method interfaces. How many parameters should this interface have, and how is the data actually passed from method to method? Every developer, regardless of their experience level, will encounter certain challenges or interfaces that may not adhere to best practices.
Initial Situation
Let's first look at the initial situation. This is an example implementation that doesn't fully implement all best practices but is intended for demonstration purposes. The class doesn't implement an interface but is instance-based. Please remember to follow best practices in a real class (see the pattern above).
Class
This example implements a relatively large class. We have a constructor that accepts various values to initialize variables within the class. Furthermore, we have a run method that executes the actual logic. Within the Run method, we first validate whether the configuration is complete and well-maintained, and then execute an additional internal Run method which implements the actual logic and may branch into further logic.
CLASS zcl_bs_demo_oo_intf1_start DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS constructor
IMPORTING config_id TYPE char10
!destination TYPE string
number_of_errors TYPE i
!log TYPE REF TO zif_aml_log.
METHODS run
EXPORTING system_id TYPE char3
!message TYPE string.
PRIVATE SECTION.
DATA config_id TYPE char10.
DATA destination TYPE string.
DATA number_of_errors TYPE i.
DATA log TYPE REF TO zif_aml_log.
METHODS is_config_valid
RETURNING VALUE(result) TYPE abap_boolean.
METHODS internal_run
IMPORTING config_id TYPE char10
!destination TYPE string
number_of_errors TYPE i
EXPORTING system_id TYPE char3
!message TYPE string.
ENDCLASS.
CLASS zcl_bs_demo_oo_intf1_start IMPLEMENTATION.
METHOD constructor.
me->config_id = config_id.
me->destination = destination.
me->number_of_errors = number_of_errors.
me->log = log.
ENDMETHOD.
METHOD run.
IF NOT is_config_valid( ).
RETURN.
ENDIF.
internal_run( EXPORTING config_id = config_id
destination = destination
number_of_errors = number_of_errors
IMPORTING system_id = system_id
message = message ).
ENDMETHOD.
METHOD is_config_valid.
IF config_id IS INITIAL.
log->add_message_text( `Config is initial` ).
ENDIF.
IF destination IS INITIAL.
log->add_message_text( `Destination is mandatory` ).
ENDIF.
IF number_of_errors <= 0.
log->add_message_text( `Insert a number bigger than 0` ).
ENDIF.
RETURN xsdbool( log->has_error( ) = abap_false ).
ENDMETHOD.
METHOD internal_run.
" Do processing
ENDMETHOD.
ENDCLASS.
Challenges
Now we have several challenges that we can actually define quite well in this class. For example, when calling the constructor, we have a relatively large interface with variables. Four different variables have already been passed here, which are handled individually internally.
METHODS constructor
IMPORTING config_id TYPE char10
!destination TYPE string
number_of_errors TYPE i
!log TYPE REF TO zif_aml_log.
The interface is therefore relatively large. If we later need to adjust the interface to include another parameter, we will also have to add a local attribute, adjust the input interface, and extend our run methods as well, since these will also accept the parameters. This means that adjusting one parameter requires us to extend three places within our code. Likewise, the interfaces are not particularly lean when we look at the implementation and the call.
internal_run( EXPORTING config_id = config_id
destination = destination
number_of_errors = number_of_errors
IMPORTING system_id = system_id
message = message ).
Another point we might be able to fix later is that we have configuration validation here. This is implemented locally and is independent of the data passed over. In a later refactoring, however, this could also mean that we have to duplicate the logic to be able to perform the validation in another object as well.
Clean ABAP
A first important piece of advice you can get is from the Clean ABAP Style Guide. This is a community-created guide that defines best practices for development in the ABAP context, but also in the ABAP context more generally. This section provides many best practices and helpful tips on how to create good and clean ABAP code. You should take a closer look at the Methods section to learn about naming, structure, and calling methods.
Our goal is to keep methods as lean as possible, minimizing the number of parameters taken in and returned. We can achieve this by bundling different elements. This allows for easy adjustments to the bundled object. This way, we avoid modifying the entire object and only need to make consistent adjustments to the content in a few key places. At the same time, the writing effort is reduced and the flexibility for later adjustments is greater.
Variant A - Structure
In the first variant, we use a structure to pass our configuration into the object, but also to output results from the object.
Definition
To do this, we first define two different structures, firstly to receive our configurations and secondly to return the result of the run. The advantage of bundled structures is that we can pass them to any method, but they themselves remain flexible, for example, to add further fields. In case of doubt, we only need to extend the structure, but not the interfaces of the methods; these remain stable.
TYPES:
BEGIN OF configuration,
config_id TYPE char10,
destination TYPE string,
number_of_errors TYPE i,
log TYPE REF TO zif_aml_log,
END OF configuration.
TYPES:
BEGIN OF run_result,
system_id TYPE char3,
message TYPE string,
END OF run_result.
Methods
If we then replace the parameters in the method with our structures, we get a manageable picture. We have exactly one input parameter and one output parameter. Each is defined as RETURNING, which makes passing to other values or direct assignment much, much simpler.
METHODS internal_run
IMPORTING config TYPE configuration
RETURNING VALUE(result) TYPE run_result.
We then replace the call in our Run method and see that the method call now only consists of one line. We can call the method directly on the return, pass the configuration, and handle everything that way. Additionally, we save ourselves the parameter assignment on the call, since we only have one input parameter and can therefore omit the name.
METHOD run.
IF NOT is_config_valid( ).
RETURN.
ENDIF.
RETURN internal_run( config ).
ENDMETHOD.
In the configuration check (IS_CONFIG_VALID), we then need to reference the structures and no longer the individual values. Otherwise, the structure of the method remains unchanged. Our Run method now returns a structure and can therefore use the RETURNING parameter. If we later want to extend the structure and return additional fields as a result, we can simply adjust the structure and thus avoid having to switch to, for example, EXPORTING.
Variant B - Object
In the second variant, we restructure our configuration and create a separate object for this purpose. For the sake of simplicity, we use a simple class without an interface here. If you want to make the whole thing testable, you should also create an interface to keep the implementation flexible. In this example, we create all attributes as public attributes and set them to READ-ONLY. They are populated via the constructor, as in the other implementation. As a crucial design decision, you can also decide here whether you want to define getters and setters for each attribute. This is easily implemented using the ABAP Development Tools. Getters and setters each have the advantage that we can check or adjust the values and attributes we receive before incorporating them into the actual implementation. In the final step, we can even add additional methods to the configuration object that function based on the data. We also adopt the IS_VALID method to test and validate the passed attributes and ensure the object's consistency.
CLASS zcl_bs_demo_oo_intf_config DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
DATA config_id TYPE char10 READ-ONLY.
DATA destination TYPE string READ-ONLY.
DATA number_of_errors TYPE i READ-ONLY.
DATA log TYPE REF TO zif_aml_log READ-ONLY.
METHODS constructor
IMPORTING config_id TYPE char10
!destination TYPE string
number_of_errors TYPE i
!log TYPE REF TO zif_aml_log.
METHODS is_valid
RETURNING VALUE(result) TYPE abap_boolean.
ENDCLASS.
CLASS zcl_bs_demo_oo_intf_config IMPLEMENTATION.
METHOD constructor.
me->config_id = config_id.
me->destination = destination.
me->number_of_errors = number_of_errors.
me->log = log.
ENDMETHOD.
METHOD is_valid.
IF config_id IS INITIAL.
log->add_message_text( `Config is initial` ).
ENDIF.
IF destination IS INITIAL.
log->add_message_text( `Destination is mandatory` ).
ENDIF.
IF number_of_errors <= 0.
log->add_message_text( `Insert a number bigger than 0` ).
ENDIF.
RETURN xsdbool( log->has_error( ) = abap_false ).
ENDMETHOD.
ENDCLASS.
Basically, we have now extracted most of the logic from the actual implementing class. This allows us to dynamically implement the configuration and also the calling class, which decouples the two objects. If we then use the configuration object in our RUN method, we can directly call the IS_VALID method from the configuration object to validate that the data is consistent. We can then simply pass the instance to the RUN method to retrieve the data from it.
METHOD run.
IF NOT config->is_valid( ).
RETURN.
ENDIF.
RETURN internal_run( config ).
ENDMETHOD.
This implementation is also very flexible, as we can easily add further attributes to the configuration class without having to modify the actual interface. Additionally, we have the flexibility to implement getters and setters, as well as other methods related to the configuration, such as saving the configuration or creating a unified log. The advantage of this is that we can now use the configuration in various other classes and it is not limited to our actual implementation.
Complete Example
All the source code shown can be found in the GitHub repository for ABAP OO examples. There you will also find further examples when it comes to class design patterns.
Conclusion
For each of the structure or object variants, there are advantages and disadvantages, and the community is not yet 100% in agreement on this. Perhaps the best approach is to decide which of the mentioned methods you want to use depending on the scenario, as this will also result in the creation of new objects or implementation costs. The most important point, however, is to focus on a lean and clean method architecture to minimize the effort required to adapt the code later.