Is it possible to pass a class to perform immediately, bypassing the intermediate variable xx?
test code work
INTERFACE my_interface1.
methods get_num returning value(rr) type i.
ENDINTERFACE.
CLASS num_counter Definition.
PUBLIC Section.
INTERFACES my_interface1.
ENDCLASS.
CLASS num_counter Implementation.
Method my_interface1~get_num.
rr = 10.
EndMethod.
ENDCLASS.
START-OF-SELECTION.
data c1 type ref to num_counter.
create object c1.
data xx type ref to my_interface1.
xx = c1.
PERFORM test using xx.
"------------------------------------------
form test
using
aaa type ref to my_interface1.
data xxi type i.
xxi = aaa->get_num( ).
WRITE xxi .
endform.
Instead, I'd like to directly do:
PERFORM test using c1.
but it was not working.
I also tried
PERFORM test using c1->my_interface1.
and
PERFORM test using c1~my_interface1.
but it was not working too.
ABAP 7.0
thanks
Related
I try to provoke a behaviour described in the ABAP Keyword Documentation 7.50 but fail. It's given with Alternative 2 of CALL METHOD - dynamic_meth:
CALL METHOD oref->(meth_name) ...
Effect
... oref can be any class reference variable ... that points to an object that contains the method ... specified in meth_name. This method is searched for first in the static type, then in the dynamic type of oref
I use the test code as given below. The static type of oref is CL1, the dynamic type CL2. Shouldn't then the dynamic CALL METHOD statement call the method M in CL1?
REPORT ZU_DEV_2658_DYNAMIC.
CLASS CL1 DEFINITION.
PUBLIC SECTION.
METHODS M.
ENDCLASS.
CLASS CL1 IMPLEMENTATION.
METHOD M.
write / 'original'.
ENDMETHOD.
ENDCLASS.
CLASS CL2 DEFINITION INHERITING FROM CL1.
PUBLIC SECTION.
METHODS M REDEFINITION.
ENDCLASS.
CLASS CL2 IMPLEMENTATION.
METHOD M.
write / 'redefinition'.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA oref TYPE REF TO cl1. " static type is CL1
CREATE OBJECT oref TYPE cl2. " dynamic type is CL2
oref->m( ). " writes 'redefinition' - that's ok
CALL METHOD oref->('M'). " writes 'redefinition' - shouldn't that be 'original'?
Update:
I'd like to answer to the (first four) comments to my original question. Because of the lengthy code snippet, I answer by augmenting my post, not by comment.
It is true that the behaviour of the code snippet of the original question is standard OO behaviour. It's also true that for calls with static method name and class, types are resolved as given by the link. But then:
Why does the ABAP Keyword Documentation make the statement I've linked?
Calls with dynamic method names do search for the method name in the dynamic type, as demonstrated by the following code piece. That's certainly not standard OO behaviour.
My question was: Apparently, the search mechanism differs from the one described. Is the description wrong or else do I miss something?
REPORT ZU_DEV_2658_DYNAMIC4.
CLASS CL_A DEFINITION.
ENDCLASS.
CLASS CL_B DEFINITION INHERITING FROM CL_A.
PUBLIC SECTION.
METHODS M2 IMPORTING VALUE(caller) TYPE c OPTIONAL PREFERRED PARAMETER caller.
ENDCLASS.
CLASS CL_B IMPLEMENTATION.
METHOD M2.
write / caller && ' calls b m2'.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA orefaa TYPE REF TO cl_a.
CREATE OBJECT orefaa TYPE cl_a. " static and dynamic type is CL_A
*orefaa->m2( 'orefa->m2( )' ). syntax error: method m2 is unknown'.
*CALL METHOD orefaa->('M2') EXPORTING caller = 'CALL METHOD orefa->("M2")'. results in exception: method m2 is unknown'.
DATA orefab TYPE REF TO cl_a. " static type is CL_A
CREATE OBJECT orefab TYPE cl_b. " dynamic type is CL_B
*orefab->m2( 'orefab->m2( )' ). results in syntax error: method m2 is unknown'.
CALL METHOD orefab->('M2') EXPORTING caller = 'CALL METHOD orefab->("M2")'. " succeeds
You are actually answering your own question there, aren't you?
In your first example, you perform a call method to the method m on a variable that's typed as cl1. The runtime looks up the class cl1, and finds the requested method m there. It then calls that method. However, your variable actually has the type cl2, a sub-class of cl1, that overrides that method m. So the call effectively reaches that redefinition of the method, not the super-class's original implementation. As you and the commenters sum it up: this is standard object-oriented behavior.
Note how in essence this has nothing to do at all with the static-vs-dynamic statement you quote from the documentation. The method m is statically present in cl1, so there is no dynamic lookup involved whatsoever. I assume you were looking for a way to probe the meaning of this statement, but this example doesn't address it.
However, your second example then precisely hits the nail on the head. Let me rewrite it again with different names to talk it through. Given an empty super class super_class:
CLASS super_class DEFINITION.
ENDCLASS.
and a sub-class sub_class that inherits it:
CLASS sub_class DEFINITION
INHERITING FROM super_class.
PUBLIC SECTION.
METHODS own_method.
ENDCLASS.
Now, as super_class is empty, sub_class does not take over any methods there. In contrast, we add a method own_method specifically to this class.
The following statement sequence then demonstrates exactly what's special with the dynamic calling:
DATA cut TYPE REF TO super_class.
cut = NEW sub_class( ).
CALL METHOD cut->('OWN_METHOD').
" runs sub_class->own_method
The runtime encounters the call method statement. It first inspects the static type of the variable cut, which is super_class. The requested method own_method is not present there. If this was all that happened, the call would now fail with a method-not-found exception. If we wrote a hard-coded cut->own_method( ), we wouldn't even get this far - the compiler would already reject this.
However, with call method the runtime continues. It determines the dynamic type of cut as being sub_class. Then it looks whether it finds an own_method there. And indeed, it does. The statement is accepted and the call is directed to own_method. This additional effort that's happening here is exactly what's described in the documentation as "This method is searched for first in the static type, then in the dynamic type of oref".
What we're seeing here is different from hard-coded method calls, but it is also not "illegal". In essence, the runtime here first casts the variable cut to its dynamic type sub_class, then looks up the available methods again. As if we were writing DATA(casted) = CAST super_class( cut ). casted->own_method( ). I cannot say why the runtime acts this way. It feels like the kind of relaxed behavior we usually find in ABAP when statements evolve throughout their lifetime and need to remain backwards-compatible.
There is one detail that needs additional addressing: the tiny word "then" in the documentation. Why is it important to say that it first looks in the static type, then in the dynamic type? In the example above, it could simply say "and/or" instead.
Why this detail may be important is described in my second answer to your question, which I posted some days ago. Let me wrap it up shortly again here, so this answer here is complete. Given an interface with a method some_method:
INTERFACE some_interface PUBLIC.
METHODS some_method RETURNING VALUE(result) TYPE string.
ENDINTERFACE.
and a class that implements it, but also adds another method of its own, with the exact same name some_method:
CLASS some_class DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES some_interface.
METHODS some_method RETURNING VALUE(result) TYPE string.
ENDCLASS.
CLASS some_class IMPLEMENTATION.
METHOD some_interface~some_method.
result = `Executed the interface's method`.
ENDMETHOD.
METHOD some_method.
result = `Executed the class's method`.
ENDMETHOD.
ENDCLASS.
Which one of the two methods is now called by CALL METHOD cut->('some_method')? The order in the documentation describes it:
DATA cut TYPE REF TO some_interface.
cut = NEW some_class( ).
DATA result TYPE string.
CALL METHOD cut->('SOME_METHOD')
RECEIVING
result = result.
cl_abap_unit_assert=>assert_equals(
act = result
exp = `Executed the interface's method` ).
Upon encountering the call method statement, the runtime checks the static type of the variable cut first, which is some_interface. This type has a method some_method. The runtime thus will continue to call this method. This, again is standard object orientation. Especially note how this example calls the method some_method by giving the string some_method alone, although its fully qualified name is actually some_interface~some_method. This is consistent with the hard-coded variant cut->some_method( ).
If the runtime acted the other way around, inspecting the dynamic type first, and the static type afterwards, it would act differently and call the class's own method some_method instead.
There is no way to call the class's own some_method, by the way. Although the documentation suggests that the runtime would consider cut's dynamic type some_class in a second step, it also adds that "In the dynamic case too, only interface components can be accessed and it is not possible to use interface reference variable to access any type of component."
The only way to call the class's own method some_method, is by changing cut's type:
DATA cut TYPE REF TO some_class.
cut = NEW some_class( ).
DATA result TYPE string.
CALL METHOD cut->('SOME_METHOD')
RECEIVING
result = result.
cl_abap_unit_assert=>assert_equals(
act = result
exp = `Executed the class's method` ).
This is rather about interface implementations than class inheritance. What the ABAP language help means is this:
Suppose you have an interface that declares a method
INTERFACE some_interface PUBLIC.
METHODS some_method RETURNING VALUE(result) TYPE string.
ENDINTERFACE.
and a class that implements it, but alongside also declares a method with the same name, of its own
CLASS some_class DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES some_interface.
METHODS some_method RETURNING VALUE(result) TYPE string.
ENDCLASS.
CLASS some_class IMPLEMENTATION.
METHOD some_interface~some_method.
result = `Executed the interface's method`.
ENDMETHOD.
METHOD some_method.
result = `Executed the class's method`.
ENDMETHOD.
ENDCLASS.
then a dynamic call on a reference variable typed with the interface will choose the interface method over the class's own method
METHOD prefers_interface_method.
DATA cut TYPE REF TO zfh_some_interface.
cut = NEW zfh_some_class( ).
DATA result TYPE string.
CALL METHOD cut->('SOME_METHOD')
RECEIVING
result = result.
cl_abap_unit_assert=>assert_equals(
act = result
exp = `Executed the interface's method` ).
ENDMETHOD.
This is actually the exact same behavior we are observing with regular calls to methods, i.e. if we provide the method's name in the code, not in a variable.
Only if the runtime cannot find a method with the given name in the static type will it start looking for a method with that name in the dynamic type. This is different from regular method calls, where the compiler will reject the missing some_interface~ and require us to add an alias for this to work.
By the way, as some people brought it up in the comments, the "static" here does not refer to CLASS-METHODS, as opposed to "instance" methods. "Static type" and "dynamic type" refer to different things, see the section Static Type and Dynmic Type in the help article Assignment Rules for Reference Variables.
The Report is required to select master data based on Selection-Screen input, map the needed fields into new export structure, transform to XML and save to local file.
Since there are multiple Reports doing to this for different types of master data I started by creating an abstract class in which I put the elements useful for all Reports and intend to create one class for each report inheriting from that class.
I then call a static method from the report which creates the instance of the report class and starts the process.
REPORT ztesten.
PARAMETERS p1 TYPE c.
PARAMETERS p2 TYPE c.
START-OF-SELECTION.
zcl_class=>main(
EXPORTING p1 = p1
p2 = p2 ).
METHOD main.
DATA(lo_class) =
NEW zcl_tradenet_export_kostl(
p1 = p1
p2 = p2 ).
lo_class->start_process( ).
ENDMETHOD.
I am struggling so far with what to use as attributes since it is generally advised to avoid using global data. Currently I store all parameters and other read only data that is selected from the database at the start of the program (this is done to avoid doing selects multiple times in loops) and then needed throughout the report as well as the export structure. If I wanted to avoid that I would have to drag them all along the call stack which seems even more impractical to me even though it uses local and not global data.
For the parameters and DB data this seems somehow ok since the attribute is only read and not changed, but as for the export structure I have more concerns since it is filled progressively. Yet dragging it along seems also impractical since it would bloat method signatures.
How would you deal with those aspects?
One final Question: Using my current approach, the number of attributes can get large fairly quickly if the selection screen has many elements or many database tables are read beforehand. Would you group them in structures to Keep the number down and make things clearer?
Create a class that provides public members to store all your parameters.
CLASS ztesten_config DEFINITION PUBLIC CREATE PUBLIC.
PUBLIC SECTION.
DATA p1 TYPE c.
DATA p2 TYPE c.
ENDCLASS.
CLASS ztesten_config IMPLEMENTATION.
ENDCLASS.
Instantiate the class and store your parameters inside.
REPORT ztesten.
PARAMETERS p1 TYPE c.
PARAMETERS p2 TYPE c.
START-OF-SELECTION.
DATA(config) = NEW ztesten_config( ).
config->p1 = p1.
config->p2 = p2.
zcl_class=>main( config ).
You can now pass that object through your call stack. This may still be annoying, but less so because it is only a single parameter. It is also the cleanest solution, because it minimizes state and coupling of your classes.
METHOD main.
DATA(lo_class) = NEW zcl_tradenet_export_kostl( ).
lo_class->start_process( config ).
ENDMETHOD.
If your objects represent processes ("bla_calculation"), not processors ("bla_calculator"), you can reduce the number of parameter passes by passing the config to the classes' constructors and letting them save in some private attribute. This requires that you instantiate the classes for each execution of the report anew.
METHOD main.
DATA(lo_class) = NEW zcl_tradenet_calculation( config ).
lo_class->start_process( ).
ENDMETHOD.
You can avoid having to pass the object through the call stack completely by applying patterns like singleton.
CLASS ztesten_config DEFINITION PUBLIC CREATE PUBLIC.
PUBLIC SECTION.
DATA p1 TYPE c.
DATA p2 TYPE c.
CLASS-METHODS get_instance
RETURNING
VALUE(result) TYPE REF TO ztesten_config.
PRIVATE SECTION.
CLASS-DATA singleton TYPE REF TO ztesten_config.
ENDCLASS.
CLASS ztesten_config IMPLEMENTATION.
METHOD get_instance.
IF singleton IS NOT BOUND.
singleton = NEW #( ).
ENDIF.
result = singleton.
ENDMETHOD.
ENDCLASS.
METHOD somewhere_inside_tradenet_export_kostl.
DATA(config) = ztesten_config=>get_instance( ).
config->p1 [...]
ENDMETHOD.
All of these patterns allow you to supply test data instead of the real report input, and to utilize your classes outside of the report context.
For the result of the report, you can follow a similar design, by producing an object that receives and stores the result data piece by piece.
Structuring parameters is always a good idea: it not only makes method signatures smaller, it also adds context which parameters belong together, and how.
Are you familiar with Clean ABAP? The section Aim for few IMPORTING parameters, at best less than three specifically recommends that "You can reduce the number of parameters by combining them into meaningful sets with structures and objects."
I'm looking at the following example, published on the ABAP Keyword Documentation, page INTERFACE. My changes are just additional lines: the REPORT statement and the statements beginning with START-OF-SELECTION.
Checking this code on an SAP system gives
Method "M1(" is unknown or PROTECTED or PRIVATE.
But isn't all defined and public? I wrote similar code, just without the interface and check works fine.
REPORT ZUTEST2.
INTERFACE i1.
DATA a1 TYPE string.
METHODS m1.
EVENTS e1 EXPORTING value(p1) TYPE string.
ENDINTERFACE.
CLASS c1 DEFINITION.
PUBLIC SECTION.
INTERFACES i1.
ENDCLASS.
CLASS c1 IMPLEMENTATION.
METHOD i1~m1.
RAISE EVENT i1~e1 EXPORTING p1 = i1~a1.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
data r type ref to c1.
create object r.
call method r->m1( ).
Firstly, the method m1 is defined in the interface i1.
You should call like below
CALL METHOD r->i1~m1( ).
If you want to call the method of your class, you may define an ALIASES in your class.
CLASS c1 DEFINITION.
PUBLIC SECTION.
INTERFACES i1.
ALIASES m1
FOR i1~m1 .
ENDCLASS.
Then you call
call method r->m1( ).
Hope it helps.
Your class construction is wrong and should be:
data: r type ref to i1.
create object r type c1.
call method r->m1( ).
I'd like to build a container ABAP class that wraps an arbitrary internal table.
My initial approach was to define a member variable of TYPE REF TO DATA and pass a reference into the constructor.
The problem is that due to the pointer the instance is still dependent on the original itab. So if the original table is freed from memory you cannot access the data anymore. I'd need to have a real copy of the table data stored within the object, so I would be able to pass the object outside the original scope of the itab.
Is there any way of achieving this in ABAP?
Sample code with references that crashes in the scenario defined in the end:
CLASS lcl_test_itab_wrapper DEFINITION LOCAL FINAL
CREATE PUBLIC.
PUBLIC SECTION.
CLASS-METHODS: access_outside_itab_scope.
METHODS: constructor IMPORTING itab TYPE table,
access_itab_data.
PRIVATE SECTION.
CLASS-METHODS: sample_itab_setup RETURNING VALUE(result) TYPE REF TO lcl_test_itab_wrapper.
DATA: table_ref TYPE REF TO data.
ENDCLASS.
CLASS lcl_test_itab_wrapper IMPLEMENTATION.
METHOD access_itab_data.
FIELD-SYMBOLS <table> TYPE table.
ASSIGN me->table_ref->* TO <table>.
WRITE:/ lines( <table> ).
ENDMETHOD.
METHOD constructor.
me->table_ref = REF #( itab ).
ENDMETHOD.
METHOD sample_itab_setup.
DATA: dummy_itab TYPE TABLE OF string.
APPEND 'test_record' TO dummy_itab.
CREATE OBJECT result EXPORTING itab = dummy_itab.
ENDMETHOD.
METHOD access_outside_itab_scope.
DATA(o_instance) = sample_itab_setup( ).
" Here it crashes as the referenced itab was freed already.
" I'd need to have a real itab copy stored in the instance
o_instance->access_itab_data( ).
ENDMETHOD.
Update: Solution based on #vwegert answer
Replace constructor reference assignment by:
CREATE DATA me->table_ref LIKE itab.
FIELD-SYMBOLS <table> TYPE table.
ASSIGN me->table_ref->* TO <table>.
<table> = itab.
You need to create a data object dynamically instead of (ab)using a statically defined one. Check the documentation of the CREATE DATA statement.
Check out the "GET REFERENCE of xx into xx" statement as well, you can save three lines of your code.
when inspecting an object-instance in the debugger, it will be printed like this:
{O:9*CLASS=CL_SOMETHING}
Is it possible to retrieve that class' identity-number 9 from a given object reference?
I want to distinguish multiple instances of the same class and print their instance-number.
I found no way using the RTTI to get that information, any advice?
As far as I know, you can't access that internal object identifier. The debugger uses some private kernel interface to do so that is not accessible to the ordinary user. You could try something like this:
CLASS lcl_object_id_map DEFINITION.
PUBLIC SECTION.
METHODS get_id
IMPORTING ir_object TYPE REF TO object
RETURNING value(r_id) TYPE sysuuid_c.
PRIVATE SECTION.
TYPES: BEGIN OF t_object_id,
object TYPE REF TO object,
id TYPE sysuuid_c,
END OF t_object_id,
tt_object_id_map TYPE HASHED TABLE OF t_object_id
WITH UNIQUE KEY object.
DATA gt_object_id_map TYPE tt_object_id_map.
ENDCLASS. "lcl_object_id_map DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_object_id_map IMPLEMENTATION.
METHOD get_id.
DATA: ls_entry TYPE t_object_id.
FIELD-SYMBOLS: <ls_entry> TYPE t_object_id.
READ TABLE gt_object_id_map
ASSIGNING <ls_entry>
WITH KEY object = ir_object.
IF sy-subrc <> 0.
ls_entry-object = ir_object.
ls_entry-id = cl_system_uuid=>create_uuid_c32_static( ).
INSERT ls_entry INTO TABLE gt_object_id_map ASSIGNING <ls_entry>.
ENDIF.
r_id = ls_entry-id.
ENDMETHOD. "get_id
ENDCLASS. "lcl_object_id_map IMPLEMENTATION
I actually found an (internal) way to get the object's internal ID in the Object Services CL_OS_CA_COMMON=>OS_GET_INTERNAL_OID_BY_REF:
CALL 'OBJMGR_GET_INFO' ID 'OPNAME' FIELD 'GET_OBJID'
ID 'OBJID' FIELD integer_oid
ID 'OBJ' FIELD ref_to_object.
Yes, this is internal stuff... Use at own risk.