
ABAP - Polymorphe Strukturen
Wie sieht es eigentlich mit Strukturen bei der dynamischen ABAP Entwicklung aus? EIne Verarbeitung war hier bisher eher generisch. Mit Polymorphic Structures kann die Entwicklung nun wieder etwas mehr Struktur bekommen.
Inhaltsverzeichnis
In diesem Artikel schauen wir uns die neuen polymorphen Strukturen an. Was geht bereits heute damit und was noch nicht? Dazu mehr Details im Artikel.
Einleitung
Die dynamische bzw. generische Entwicklung ist die flexibelste Entwicklung, die du nutzen kannst, bedeutet aber auch im Umkehrschluss, dass du sehr viel Quellcode erzeugst, um eine kleine Menge von Daten zu verarbeiten oder generische Code-Bestandteile bereitzustellen. Dies ist meistens dem geschuldet, dass hier häufig mehr Überprüfungen, Variablen und Zuweisungen nötig sind, weil nicht direkt über den Komponentennamen zugegriffen werden kann. So ist zum Beispiel auch ein sehr häufiges und umfangreiches Thema das Mapping von Strukturen und am Ende auch der Zugriff auf die verschiedenen Komponenten der Strukturen. Hierzu hat SAP eine erste Lösung mit polymorphen Strukturen zur Verfügung gestellt, die einen Teil der Generik abnehmen soll, um so einfacheren und kürzeren ABAP-Quellcode schreiben zu können. Polymorphe Strukturen wurden mit Release 2608 im ABAP Environment und in der Public Cloud eingeführt und werden wahrscheinlich mit dem Release 2027 in On-Prem und Private Cloud ausgerollt.
Vorbereitung
Vorab brauchen wir noch einige Strukturen, Tabellen und Daten als Vorbereitung. Dazu legen wir uns zwei Datentypen an, die in allen Strukturen wiederverwendet werden sollen. Zusätzlich legen wir uns drei Strukturen an: eine für den Supplier und den Customer, dort sind jeweils der Identifier und der Name enthalten, und eine Struktur, in der beide Felder fehlen, um später Mapping-Einstellungen testen zu können.
TYPES identifier TYPE c LENGTH 12.
TYPES name TYPE string.
TYPES:
BEGIN OF none_field,
some_id TYPE c LENGTH 10,
location TYPE string,
value TYPE int8,
END OF none_field.
TYPES none_fields TYPE STANDARD TABLE OF none_field WITH EMPTY KEY.
TYPES:
BEGIN OF customer,
customer_id TYPE c LENGTH 10,
location TYPE string,
identifier TYPE identifier,
name TYPE name,
value TYPE int8,
END OF customer.
TYPES customers TYPE STANDARD TABLE OF customer WITH EMPTY KEY.
TYPES:
BEGIN OF supplier,
supplier_id TYPE c LENGTH 10,
contract_value TYPE p LENGTH 16 DECIMALS 2,
value TYPE int8,
identifier TYPE identifier,
name TYPE name,
END OF supplier.
TYPES suppliers TYPE STANDARD TABLE OF supplier WITH EMPTY KEY.
TYPES generic_input TYPE ANY STRUCTURE CONTAINING identifier TYPE identifier
name TYPE name.
TYPES generic_inputs TYPE STANDARD TABLE OF generic_input WITH EMPTY KEY.
Damit wir auch gleich Daten haben, legen wir dann jeweils für den Supplier und den Customer zwei Datensätze in Tabellenform an, ebenso für die nicht vorhandenen Objekte. Zum Abschluss lesen wir jeweils die erste Zeile aus der Tabelle, um einmal mit Tabellen und dann auch mit Strukturen arbeiten zu können.
FINAL(customers) = VALUE customers( ( customer_id = 'C0001'
location = `Teststreet 1, 51100 Cologne`
identifier = '10'
name = 'John Doe'
value = 98 )
( customer_id = 'C0002'
location = `Test Allee 3, 21230 Berlin`
identifier = '20'
name = 'Jane Doe'
value = 13 ) ).
FINAL(suppliers) = VALUE suppliers( ( supplier_id = 'S0001'
contract_value = '10000.00'
identifier = '20'
name = 'Jane Doe'
value = 17 )
( supplier_id = 'S0002'
contract_value = '15000.00'
identifier = '10'
name = 'John Doe'
value = 23 ) ).
FINAL(nones) = VALUE none_fields( ( some_id = 'N0001'
location = `Teststreet 1, 51100 Cologne`
value = 40 )
( some_id = 'N0002'
location = `Test Allee 3, 21230 Berlin`
value = 16 ) ).
FINAL(customer) = customers[ 1 ].
FINAL(supplier) = suppliers[ 1 ].
FINAL(none) = nones[ 1 ].
Definition
Schauen wir uns erst einmal die Definition des Datentyps an und wie wir diesen anlegen sowie verwenden können.
Anlage
Grundsätzlich würden wir erwarten, dass wir eine Struktur definieren. Das heißt, wir definieren einen neuen Typ mit einem Namen und geben diesem den Typ ANY STRUCTURE. Damit haben wir eine generische Struktur definiert. Am Ende definieren wir über den Zusatz CONTAINING, welche Felder mindestens in der Struktur vorhanden sein müssen. Die Felder haben dann entsprechend einen Namen und einen Typ, der definiert wird. Vom Compiler und zur Laufzeit wird dann geprüft, ob es ein entsprechendes Element mit dem Namen gibt und ob der Typ, falls er nicht zu 100 % passt, zumindest in den Zieltyp gecastet werden kann.
TYPES generic_input TYPE ANY STRUCTURE CONTAINING
identifier TYPE identifier
name TYPE name.
Wenn wir eine Struktur anlegen können, können wir natürlich auch eine Tabelle definieren. Hier verwenden wir den Standard, indem wir den Namen des Typs angeben, als Typ zum Beispiel STANDARD TABLE oder SORTED TABLE verwenden und einen Schlüssel verwenden. Damit definieren wir einen Tabellentyp, der dann von unserem generischen Strukturtyp erbt, womit wir dann zum Beispiel in eine Methode auch Tabellen und nicht nur einzelne Datensätze übergeben können.
TYPES generic_inputs TYPE STANDARD TABLE OF generic_input WITH EMPTY KEY.
Schnittstellen
Diesen Datentyp können wir dann verwenden, um innerhalb einer Methodenschnittstelle einen Parameter zu definieren. Hier gibt es zum Standard keine Abweichungen, sondern hier arbeitest du ganz normal mit dem Typen.
METHODS extract_name
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out
!input TYPE generic_input.
Variablen
Etwas anders sieht es allerdings aus, wenn du eine Inline-Deklaration oder eine Typisierung eines lokalen Elements durchführen möchtest. Grundsätzlich können wir den Zusatz schreiben und den Typ auch verwenden. Allerdings erhalten wir dann vom Compiler direkt eine Fehlermeldung.
DATA local TYPE generic_input.
Diese Datentypen sind nicht für die direkte Deklaration gedacht, sondern sollten vor allem für Schnittstellen, Parameter und Feldsymbole verwendet werden. Dies zeigt auch die Fehlermeldung, die wir erhalten, wodurch wir unseren ABAP-Code dann nicht aktivieren können.
Zuweisung
Schauen wir uns nun verschiedene Beispiele an, wie wir Daten zuweisen und diese verwenden können.
Methoden
Oben haben wir verschiedene Methoden definiert, um Daten entgegenzunehmen und über eine generische Struktur zu verarbeiten. Deshalb müssen wir hier einfach die Methode aufrufen und übergeben im Input-Parameter den dynamischen Typ, in dem Fall den Customer und den Supplier. Diese unterschiedlichen Typen erzeugen aber keine Fehlermeldungen und werden eins zu eins übernommen. Dies funktioniert für die Strukturen und Tabellen.
extract_name( out = out
input = customer ).
extract_name( out = out
input = supplier ).
Etwas anders sieht es allerdings aus, wenn wir den Typennamen verwenden: Da dieser kein Feld enthält, welches im CONTAINING-Zusatz definiert ist, erhalten wir bereits vom Compiler eine Fehlermeldung. Das liegt daran, dass wir einen typisierten Datentyp haben und das System erkennt, dass dieser kein entsprechendes Mindestfeld besitzt.
Schleifen
Schauen wir auf die Verarbeitung von Tabellen innerhalb der Methoden. Hierzu gehen wir in die Methode EXTRACT_NAMES, wo wir eine ganze Tabelle generisch übergeben. Hier ist es nicht so einfach möglich, im LOOP direkt eine Inline-Deklaration durchzuführen. Wir würden hier eine Fehlermeldung erhalten, da wir mit einem generischen Typ arbeiten. Wollen wir eine Referenz verwenden, dann müssen wir diese zuvor mit REF TO DATA definieren.
DATA input TYPE REF TO data.
LOOP AT inputs REFERENCE INTO input.
extract_name( out = out
input = input->* ).
ENDLOOP.
Bei Feldsymbolen sieht es hier etwas anders aus: Hier müssen wir nicht erst ein Feldsymbol mit TYPE ANY definieren, sondern können direkt mit einer Inline-Deklaration arbeiten und das Feldsymbol an unsere Methode übergeben.
LOOP AT inputs ASSIGNING FIELD-SYMBOL(<input>).
extract_name( out = out
input = <input> ).
ENDLOOP.
Verarbeitung
Wir haben uns die Definition und die Weitergabe angeschaut. Nun geht es in die eigentliche Verarbeitung und darum, was wir an Code sparen können.
Aktuell
Wie sieht es also aus, wenn wir normalerweise generisch auf zwei Attribute zugreifen wollen, in diesem Fall den Identifier und den Namen, um diese in der Verarbeitung zu extrahieren oder auszugeben? Dazu müssen wir ein ASSIGN COMPONENT durchführen, den Namen der Komponente angeben und diesen einem Feldsymbol zuordnen. Gleichzeitig müssen wir aber noch überprüfen, ob die Zuordnung auch wirklich geklappt hat oder ob es dieses Feld vielleicht gar nicht in der Struktur gibt. Am Ende können wir dann die Ausgabe für die beiden Informationen vornehmen.
ASSIGN COMPONENT 'IDENTIFIER' OF STRUCTURE input TO FIELD-SYMBOL(<identifier>).
IF sy-subrc <> 0.
RETURN.
ENDIF.
ASSIGN COMPONENT 'NAME' OF STRUCTURE input TO FIELD-SYMBOL(<name>).
IF sy-subrc <> 0.
RETURN.
ENDIF.
out->write( |{ <identifier> } - { <name> }| ).
Neu
Schauen wir uns nun die Verarbeitung mit den neuen Strukturen an, dann sehen wir direkt, dass wir den ganzen Overhead der dynamischen Entwicklung gar nicht mehr benötigen. Dadurch, dass wir definiert haben, dass der Identifier und der Name mindestens in der Struktur sein müssen, können wir hier in der Methode auch direkt damit arbeiten und auf diese beiden Feldnamen zugreifen. Es ist keine Überprüfung auf Vorhandensein notwendig.
out->write( |{ input-identifier } - { input-name }| ).
Ebenso bekommen wir auch direkt Vorschläge aus dem Autocomplete, was die Arbeit für uns in der Entwicklung wieder einfacher macht, ohne das wir vorhandene Felder erraten müssen.
Vollständies Beispiel
In diesem Kapitel findest du die vollständige ausführbare Klasse, um das Beispiel in deinem System nachstellen zu können. Du brauchst dafür aber auch das passende ABAP Release.
CLASS zcl_bs_demo_poly_structures DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
TYPES identifier TYPE c LENGTH 12.
TYPES name TYPE string.
TYPES:
BEGIN OF none_field,
some_id TYPE c LENGTH 10,
location TYPE string,
value TYPE int8,
END OF none_field.
TYPES none_fields TYPE STANDARD TABLE OF none_field WITH EMPTY KEY.
TYPES:
BEGIN OF customer,
customer_id TYPE c LENGTH 10,
location TYPE string,
identifier TYPE identifier,
name TYPE name,
value TYPE int8,
END OF customer.
TYPES customers TYPE STANDARD TABLE OF customer WITH EMPTY KEY.
TYPES:
BEGIN OF supplier,
supplier_id TYPE c LENGTH 10,
contract_value TYPE p LENGTH 16 DECIMALS 2,
value TYPE int8,
identifier TYPE identifier,
name TYPE name,
END OF supplier.
TYPES suppliers TYPE STANDARD TABLE OF supplier WITH EMPTY KEY.
TYPES generic_input TYPE ANY STRUCTURE CONTAINING identifier TYPE identifier
name TYPE name.
TYPES generic_inputs TYPE STANDARD TABLE OF generic_input WITH EMPTY KEY.
METHODS extract_name_generic
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out
!input TYPE any.
METHODS extract_name
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out
!input TYPE generic_input.
METHODS extract_names
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out
inputs TYPE generic_inputs.
ENDCLASS.
CLASS zcl_bs_demo_poly_structures IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
FINAL(customers) = VALUE customers( ( customer_id = 'C0001'
location = `Teststreet 1, 51100 Cologne`
identifier = '10'
name = 'John Doe'
value = 98 )
( customer_id = 'C0002'
location = `Test Allee 3, 21230 Berlin`
identifier = '20'
name = 'Jane Doe'
value = 13 ) ).
FINAL(suppliers) = VALUE suppliers( ( supplier_id = 'S0001'
contract_value = '10000.00'
identifier = '20'
name = 'Jane Doe'
value = 17 )
( supplier_id = 'S0002'
contract_value = '15000.00'
identifier = '10'
name = 'John Doe'
value = 23 ) ).
FINAL(nones) = VALUE none_fields( ( some_id = 'N0001'
location = `Teststreet 1, 51100 Cologne`
value = 40 )
( some_id = 'N0002'
location = `Test Allee 3, 21230 Berlin`
value = 16 ) ).
FINAL(customer) = customers[ 1 ].
FINAL(supplier) = suppliers[ 1 ].
FINAL(none) = nones[ 1 ].
out->write( `Extract Generic:` ).
extract_name_generic( out = out
input = customer ).
extract_name_generic( out = out
input = supplier ).
extract_name_generic( out = out
input = none ).
out->write( `Extract Name:` ).
extract_name( out = out
input = customer ).
extract_name( out = out
input = supplier ).
* extract_name( out = out
* input = none ).
out->write( `Extract Names:` ).
extract_names( out = out
inputs = customers ).
extract_names( out = out
inputs = suppliers ).
* extract_names( out = out
* inputs = nones ).
ENDMETHOD.
METHOD extract_name_generic.
ASSIGN COMPONENT 'IDENTIFIER' OF STRUCTURE input TO FIELD-SYMBOL(<identifier>).
IF sy-subrc <> 0.
RETURN.
ENDIF.
ASSIGN COMPONENT 'NAME' OF STRUCTURE input TO FIELD-SYMBOL(<name>).
IF sy-subrc <> 0.
RETURN.
ENDIF.
out->write( |{ <identifier> } - { <name> }| ).
ENDMETHOD.
METHOD extract_name.
out->write( |{ input-identifier } - { input-name }| ).
ENDMETHOD.
METHOD extract_names.
DATA input TYPE REF TO data.
LOOP AT inputs REFERENCE INTO input.
extract_name( out = out
input = input->* ).
ENDLOOP.
LOOP AT inputs ASSIGNING FIELD-SYMBOL(<input>).
extract_name( out = out
input = <input> ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Fazit
Die dynamische Programmierung und der Zugriff auf Felder innerhalb von Strukturen können damit erleichtert werden, wenn wir die polymorphen Strukturen auch wirklich verwenden. Kleinere Hindernisse kann es bei der Typisierung oder bei der Übergabe trotzdem noch geben, ebenso wenn wir komplett dynamisch arbeiten wollen und keine Informationen vom Compiler erhalten. Grundsätzlich bieten sie jedoch einen echten Mehrwert für die Entwicklung.
Weiter Informationen:
SAP Help - ANY STRUCTURE CONTAINING
SAP Help - Demo Program


