
ABAP OO - Methodenschnittstellen
Wie sollten Methodenschnittstellen aktuell aussehen und wie erreichst du diesen Zustand? In diesem Artikel werden wir die Frage klären.
Inhaltsverzeichnis
Wie sollte in der heutigen Zeit eigentlich eine Methodenschnittstelle aussehen und was bringen dir eigentlich kleine Schnittstellen bei deiner täglichen Arbeit? Diese Frage wollen wir uns einmal genauer anschauen und dabei auch die Vergangenheit in SAP etwas genauer beleuchten.
Vergangenheit
In der Vergangenheit wurden Schnittstellen recht einfach definiert und für jeden Parameter gab es eine eigene Variable. Bei RFC Funktionsbausteinen macht dies auch Sinn, da komplexe Datentypen von Außen schwer ansprechbar sind. Ebenso existieren im System BAPIs in verschiedenen Ausprägungen und Komplexitäten. Wenn du in der letzten Zeit mal einen in dein Coding implementiert hast, wirst du wissen wovon wir reden, die Schnittstellen sind meist nicht sehr übersichtlich.
Herausforderung
In der objektorientierten Programmierung sind solche "Monsterschnittstellen" nicht sehr praktikabel, da sie sich schlecht in ein IF Statement packen lassen und die Methoden nicht besonders klein machen. Stell dir deshalb einfach mal das folgende Szenario vor:
- Deine Klasse muss erweitert werden und du möchtest einen oder mehrere neue Parameter über die Main Methode in die Klasse einschleusen, um im Inneren damit zu arbeiten.
- Deine Schnittstelle nutzt einzelne Parameter, damit du die Datenübergabe ordentlich regeln kannst.
Die Klasse könnte nun wie folgt aussehen und implementiert zur Demonstration nur eine einfache Logik, bei der wir die Parameter an andere Methoden weitergeben, um sie dort zu verwenden.
CLASS zcl_interface_not_clean DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
tt_r_bukrs TYPE RANGE OF t001-bukrs,
tt_r_waers TYPE RANGE OF t001-waers,
tt_t001 TYPE STANDARD TABLE OF t001 WITH EMPTY KEY.
METHODS:
main
IMPORTING
it_r_bukrs TYPE tt_r_bukrs
it_r_waers TYPE tt_r_waers
id_butxt TYPE t001-butxt
id_test TYPE abap_bool.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
select_data
IMPORTING
it_r_bukrs TYPE tt_r_bukrs
it_r_waers TYPE tt_r_waers
EXPORTING
et_t001 TYPE tt_t001,
update_data
IMPORTING
it_t001 TYPE tt_t001
id_butxt TYPE t001-butxt
id_test TYPE abap_bool.
ENDCLASS.
CLASS zcl_interface_not_clean IMPLEMENTATION.
METHOD main.
select_data(
EXPORTING
it_r_bukrs = it_r_bukrs
it_r_waers = it_r_waers
IMPORTING
et_t001 = DATA(lt_t001)
).
update_data(
EXPORTING
it_t001 = lt_t001
id_butxt = id_butxt
id_test = id_test
).
ENDMETHOD.
METHOD select_data.
SELECT *
FROM t001
WHERE bukrs IN @it_r_bukrs
AND waers IN @it_r_waers
INTO TABLE @et_t001.
ENDMETHOD.
METHOD update_data.
DATA(lt_t001) = it_t001.
LOOP AT lt_t001 REFERENCE INTO DATA(lr_t001).
lr_t001->butxt = id_butxt.
ENDLOOP.
IF id_test = abap_false.
UPDATE t001 FROM TABLE lt_t001.
ENDIF.
ENDMETHOD.
ENDCLASS.
Wollen wir zum Beispiel eine neue Range zur Abfrage der Daten von der Tabelle T001 implementieren, müssen wir einige Stellen anpassen, was mehr Zeit dauert und aufwändiger ist.
Lösung
Dazu gibt es auch im Repository von Clean ABAP einen entsprechenden Eintrag. Es wird ebenso empfohlen einen Importing und einen Returning Parameter zu verwenden, da sich die Methoden so am Einfachsten nutzen lassen. Die Herausforderung liegt nun dabei sein Schnittstellen entsprechend aufzubauen. Hier stehen dir im Grunde zwei bewährte Methoden zur Verfügung:
- Verwendung einer Struktur mit den entsprechenden Merkmalen auf der Strukturebene
- Verwendung eines Objekts als Datencontainer
Beide Methoden haben auch ihren Charme, wir empfehlen aber das Objekt, da du damit am Flexibelsten bist und weitere Logik, sowie Validierungen direkt an die Daten hängen kannst. Außerdem ist ein Objekt änderbar, was bei einer Struktur nicht möglich ist, die nur als Importing übergeben wird.
Dazu legen wir im nächsten Schritt eine Konfiguration an, der Einfachheit verwenden wir kein Interface, sondern bauen direkt die Klasse auf. Wie wir dir in ABAP Unit erklärt haben, sollte aber zur leichteren Testbarkeit ein Interface verwendet werden:
CLASS zcl_test_config DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
tt_r_bukrs TYPE RANGE OF t001-bukrs,
tt_r_waers TYPE RANGE OF t001-waers.
DATA:
mt_r_bukrs TYPE tt_r_bukrs,
mt_r_waers TYPE tt_r_waers,
md_butxt TYPE t001-butxt,
md_test TYPE abap_bool.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_test_config IMPLEMENTATION.
ENDCLASS.
An dieser Stelle bauen wir nun unsere Ausgangsklasse einmal um und verwenden das neue Konfigurationsobjekt. Bei der Übergabe wird die Schnittstelle schon einmal viel kleiner und ist nun leicht erweiterbar.
CLASS zcl_interface_clean DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
tt_t001 TYPE STANDARD TABLE OF t001 WITH EMPTY KEY.
METHODS:
main
IMPORTING
io_config TYPE REF TO zcl_test_config.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
select_data
IMPORTING
io_config TYPE REF TO zcl_test_config
RETURNING VALUE(rt_t001) TYPE tt_t001,
update_data
IMPORTING
it_t001 TYPE tt_t001
io_config TYPE REF TO zcl_test_config.
ENDCLASS.
CLASS zcl_interface_clean IMPLEMENTATION.
METHOD main.
DATA(lt_t001) = select_data( io_config ).
update_data( it_t001 = lt_t001 io_config = io_config ).
ENDMETHOD.
METHOD select_data.
SELECT *
FROM t001
WHERE bukrs IN @io_config->mt_r_bukrs
AND waers IN @io_config->mt_r_waers
INTO TABLE @rt_t001.
ENDMETHOD.
METHOD update_data.
DATA(lt_t001) = it_t001.
LOOP AT lt_t001 REFERENCE INTO DATA(lr_t001).
lr_t001->butxt = io_config->md_butxt.
ENDLOOP.
IF io_config->md_test = abap_false.
UPDATE t001 FROM TABLE lt_t001.
ENDIF.
ENDMETHOD.
ENDCLASS.
Fazit
Mit der entsprechenden Technik und einer gewissen Voraussicht sollte es möglich sein, die Schnittstellen sehr klein zu halten und deine Objekte in Zukunft offen für Erweiterungen zu lassen, ohne die Schnittstellen komplett umbauen und erweitern zu müssen.