ABAP OO - Chaining and casting
This article is about the chaining of method and object calls, we would also like to explain the background to casting to you.
We would like to bring you closer to the topic of chaining and how it can make your source code simpler and more structured. We will also go into the topic of casting and where you can and sometimes even have to use it in development.
Since New ABAP (from 7.40), more and more functions have been implemented in the programming language, which continue to replace the one-line commands in the language and thus ensure a more modern and clear programming style. With this clarity, however, there are also hidden dangers, because the elements can now be partially chained and can thus cause confusion and difficult-to-read source code.
Let's take a look at a relatively complex example and try to unravel it a little. In the first step, let's not pay attention to the context of the example, but orientate ourselves on the accesses:
go_invoice_manager->mt_invoices[ 4 ]-ref->do_payment( ).
Let's break down the entire chain step by step and look at the individual elements:
- go_invoice_manager - Global reference to an object
- mt_invoices - Public attribute of the reference go_invoice_manager and a table by name
- [ 4 ] - Read the 4th line of the table
- -ref - Access to a field of the 4th line with the name "ref", which in turn is a reference
- do_payment( ) - Calling the method of the object
As you have seen in a more complex example, the chaining of the individual options can also slow down your reading speed of coding, as you first have to decode and interpret the individual statements in order to understand the whole.
Hint: In the Clean ABAP it is described that a chain should be carried out up to a maximum depth of three levels, otherwise the clarity would be lost.
This gives you different types of chaining options and we want to show you a few different ones here. The list is not exhaustive and there are no limits to the imagination:
" 1) DATA(go_invoice_manager) = NEW lcl_invoice_factory( ). go_invoice_manager->add( NEW lcl_bill( '99.99' ) ). go_invoice_manager->add( NEW lcl_bill( '12.50' ) ). " 2) LOOP AT go_invoice_manager->get_invoices( ) INTO DATA(gs_invoice). gs_invoice-ref->do_payment( ). ENDLOOP. " 3) IF go_invoice_manager->has_entries( ) = abap_true. ENDIF. " 4) NEW lcl_bill( )->pay( ).
In addition, a short explanation of the facts:
- Creation of the instance directly upon transfer to the method and thus omitting the intermediate variables.
- Use of the return parameter to iterate over the result with a loop.
- Use in a query and validation of the return parameter. In this case, the comparison against ABAP_TRUE can also be omitted. More on this in this article.
- Creation of the instance and calling of the method, the reference is no longer required.
In the case of casting, we are actually only talking about the assignment of an instance to a reference that does not have the same type as the instance, but the two objects are known to one another through an interface or via inheritance. Let's take the following class model, which we have already used in this article:
To do this, we now create a reference variable that refers to the superclass LCL_VEHICLE. In the first example we create a new instance of the object and can call the SPEED_UP method directly.
DATA: go_vehicle TYPE REF TO lcl_vehicle. go_vehicle = NEW #( ). go_vehicle->speed_up( ).
In the next step we create a new instance of the type LCL_CAR, i.e. a special variant of LCL_VEHICLE, and assign the object to our reference. The assignment works because the variable is of an equivalent or more general derivation of the class.
go_vehicle = NEW lcl_car( ). go_vehicle->speed_up( ).
However, we do not get to the special method of the class LCL_CAR via the reference variable, since the variable refers to LCL_VEHICLE and thus only knows the SPEED_UP method. At this point we have to do a CAST first.
DATA(go_casted_car) = CAST lcl_car( go_vehicle ). go_casted_car->open_luggage_space( ).
After the CAST we have a new reference variable of the type LCL_CAR via which we can then call the corresponding method and thus also use the functions of LCL_CAR. This is also possible with a chaining.
CAST lcl_car( go_vehicle )->open_luggage_space( ).
Let's take the example from the beginning again and try to cast our reference of the type LCL_VEHICLE to the class LCL_CAR. In this case we will get an exception because the more general instance cannot be cast on a more specific instance. The more specific instance has more specific characteristics and more attributes that may not yet have been initialized.
TRY. DATA(go_casted_vehicle) = CAST lcl_car( go_vehicle ). go_casted_vehicle->open_luggage_space( ). CATCH cx_sy_move_cast_error. ENDTRY.
The complete example from this chapter therefore looks like this:
DATA: go_vehicle TYPE REF TO lcl_vehicle. go_vehicle = NEW #( ). go_vehicle->speed_up( ). TRY. DATA(go_casted_vehicle) = CAST lcl_car( go_vehicle ). go_casted_vehicle->open_luggage_space( ). CATCH cx_sy_move_cast_error. ENDTRY go_vehicle = NEW lcl_car( ). go_vehicle->speed_up( ). DATA(go_casted_car) = CAST lcl_car( go_vehicle ). go_casted_car->open_luggage_space( ). CAST lcl_car( go_vehicle )->open_luggage_space( ).
Working with objects and instances can be a bit confusing at first, but it follows a very precise logic. With inheritance, you can easily reuse source code, but you have to pay attention to the specialties when accessing it.