ABAP OO - Redefinition and Getter/Setter
In this article we look at the topic of redefinition and how getters and setters can help you with uniform interfaces.
Table of contents
Today we will talk about redefinition and how it can help you build better classes and structure code more efficiently. As a second topic, we look at the use of getter and setter methods and how they make your interfaces better.
Redefinition
The redefinition is part of the inheritance of classes and can be used to overwrite inherited methods and thus provide them with new logic. It is important that the name of the method and the interface do not change. These remain stable and only a new flow logic is implemented. This means that the object remains stable when it is transferred to another interface, but the output and the data can change accordingly.
To do this, we define a simple class that should perform a calculation. The method takes a table of numbers and returns the corresponding sum. At this point you should note that your class is not FINAL, otherwise you can no longer inherit from it.
CLASS zcl_bs_demo_calculator DEFINITION PUBLIC CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
ts_numbers TYPE i,
tt_numbers TYPE STANDARD TABLE OF ts_numbers WITH EMPTY KEY.
METHODS:
calculate
IMPORTING
it_numbers TYPE tt_numbers
RETURNING VALUE(rd_result) TYPE i.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_bs_demo_calculator IMPLEMENTATION.
METHOD calculate.
LOOP AT it_numbers INTO DATA(ld_number).
rd_result += ld_number.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Now we would like to implement a class that has the same interfaces as the first class, but the calculation is a little different. In this case we define a new class and inherit from our first class. Now all you have to do is redefine the CALCULATE method and then re-implement the logic. The interface remains stable and cannot be changed. So that you can reimplement the method locally, you have to create the method in the class and overwrite the method with the keyword REDEFINITION. You can then carry out the implementation again.
CLASS zcl_bs_demo_calc_redefinition DEFINITION PUBLIC CREATE PUBLIC
INHERITING FROM zcl_bs_demo_calculator.
PUBLIC SECTION.
METHODS:
calculate REDEFINITION.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_bs_demo_calc_redefinition IMPLEMENTATION.
METHOD calculate.
rd_result = 1.
LOOP AT it_numbers INTO DATA(ld_number).
rd_result *= ld_number.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Now we can test the two classes. To do this, we define a console application and define the reference based on the original class and fill the numbers with 1-5 in order to have a basis for the calculation. Then we create an instance of the class and output the result to the console.
CLASS zcl_bs_demo_calc_usage DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_bs_demo_calc_usage IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA:
lo_calculator TYPE REF TO zcl_bs_demo_calculator.
DATA(lt_numbers) = VALUE zcl_bs_demo_calculator=>tt_numbers(
( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 )
).
lo_calculator = NEW zcl_bs_demo_calculator( ).
out->write( |Original class: { lo_calculator->calculate( lt_numbers ) }| ).
lo_calculator = NEW zcl_bs_demo_calc_redefinition( ).
out->write( |Redefined class: { lo_calculator->calculate( lt_numbers ) }| ).
ENDMETHOD.
ENDCLASS.
In the example we use the same reference variable to simulate the stable interface and create an instance of the class before we call the CALCULATE method. Here you can see the result of the calculation, the two differently defined methods were called.
Getter/Setter
Most classes also have attributes that are used within the class, but also provide data to the outside world. Such attributes can be defined as PUBLIC and thus made available via the object. This means that a user can access the attribute at any time, read the data, but also change it. This behavior is not always desired and has a decisive disadvantage, you no longer have any influence on the attribute before it is given to the outside world and this must remain stable at all times (data type).
Let's take a look at a small example of a class that has a table that accepts messages that are added via the method ADD_MESSAGE.
CLASS zcl_bs_demo_public_data DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
DATA:
mt_messages TYPE string_table.
METHODS:
add_message
IMPORTING
id_message TYPE string.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_bs_demo_public_data IMPLEMENTATION.
METHOD add_message.
INSERT id_message INTO TABLE mt_messages.
ENDMETHOD.
ENDCLASS.
In this fictional example we are adding various messages to the class and in the middle of this process we are deleting the messages. This is to simulate that while we were executing another piece of source code initialized the class. All reports up to then are lost and the result could be falsified.
DATA(lo_public_data) = NEW zcl_bs_demo_public_data( ).
lo_public_data->add_message( `Message 1` ).
CLEAR lo_public_data->mt_messages.
lo_public_data->add_message( `Message 2` ).
lo_public_data->add_message( `Message 3` ).
To prevent this, we can change the visibility of the attribute and set it to PROTECTED or PRIVATE. This means that it can no longer be changed from the outside and our messages are safe from unwanted changes. How do we get the messages from outside? For this we implement a getter, this is a method that starts with GET_, usually has the name of the attribute after that and has a returning parameter that returns the attribute. The changed class could now look like this:
CLASS zcl_bs_demo_private_data DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
add_message
IMPORTING
id_message TYPE string,
get_messages
RETURNING VALUE(rt_result) TYPE string_table.
PROTECTED SECTION.
PRIVATE SECTION.
DATA:
mt_messages TYPE string_table.
ENDCLASS.
CLASS zcl_bs_demo_private_data IMPLEMENTATION.
METHOD add_message.
INSERT id_message INTO TABLE mt_messages.
ENDMETHOD.
METHOD get_messages.
rt_result = mt_messages.
ENDMETHOD.
ENDCLASS.
Our messages MT_MESSAGES are now protected against unwanted changes. A setter, on the other hand, is a method that begins with SET_, followed by the name of the attribute and has an importing parameter. This method sets the attribute in the class with a new value.
Getters and setters have several advantages when using them:
- Implementation of additional check and filter code
- Protection of the attributes from unwanted changes
- Uniform and stable interfaces
- Opportunities to intervene in the process
Read Only
In addition to the getter and setter methods, there is also another way of working with public attributes and protecting them from access at the same time. You can also assign the READ-ONLY addition to the attribute so that the attribute is only released for read access. In addition the changed class from the previous section:
CLASS zcl_bs_demo_readonly_data DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
DATA:
mt_messages TYPE string_table READ-ONLY.
METHODS:
add_message
IMPORTING
id_message TYPE string.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_bs_demo_readonly_data IMPLEMENTATION.
METHOD add_message.
INSERT id_message INTO TABLE mt_messages.
ENDMETHOD.
ENDCLASS.
If you now try to write to the attribute, you will get an error while the compile is running and you can no longer activate your code. Here is the message from Eclipse:
The attribute is now protected against write access, but you also lose the advantages of the getter and setter methods and can no longer intervene in the code or implement your own checks. We only recommend this variant to a limited extent.
Conclusion
Today it was about the redefinition of methods in order to be able to implement different logic in the same methods and to create the same classes with different behavior. You have also learned how to efficiently manage your attributes in a class while maintaining full control over your data.