ABAP OO - Verkettung und Casting
In diesem Artikel geht es um Verkettung von Methoden- und Objektaufrufen, weiterhin möchten wir dir den Hintergrund zum Casting erläutern.
Inhaltsverzeichnis
Wir möchten dir das Thema Verkettung etwas näher bringen und wie es deinen Quellcode einfacher und strukturierter machen kann. Weiterhin werden wir auf das Thema Casting eingehen und wo du es in der Entwicklung einsetzen kannst und teilweise sogar musst.
Verkettung
Seit dem Modernen ABAP (ab 7.40) sind immer mehr Funktionen in der Programmiersprache implementiert worden, die immer weiter die Ein-Zeilen-Kommandos in der Sprache ablösen und so für einen moderneren und übersichtlicheren Programmierstil sorgen. Mit dieser Übersichtlichkeit kommen aber auch versteckte Gefahren, denn die Elemente lassen sich nun teilweise verketten und können so für Verwirrung und schlecht lesbaren Quellcode sorgen.
Beispiel
Schauen wir uns dazu einmal ein relativ komplexes Beispiel an und versuchen es etwas zu entwirren. Achten wir im ersten Schritt einmal nicht auf den Kontext des Beispiels, sondern orientieren uns an den Zugriffen:
go_invoice_manager->mt_invoices[ 4 ]-ref->do_payment( ).
Lösen wir dazu die gesamte Kette einmal Schritt für Schritt auf und schauen uns die einzelnen Elemente an:
- go_invoice_manager - Globale Referenz auf ein Objekt
- mt_invoices - Öffentliches Attribut der Referenz go_invoice_manager und vom Namen her eine Tabelle
- [ 4 ] - Lesen (READ) der 4ten Zeile der Tabelle
- -ref - Zugriff auf ein Feld der 4ten Zeile mit dem Namen "ref", welches wiederrum eine Referenz ist
- do_payment( ) - Aufruf der Methode des Objekts
Wie du an einem komplexeren Beispiel gesehen hast, kann die Verkettung der einzelnen Möglichkeiten aber auch dein Lesetempo von Coding verlangsamen, da du die einzelnen Statements erst einmal dekodieren und interpretieren musst, um das Ganze zu verstehen.
Hinweis: Im Clean ABAP ist beschrieben, dass man eine Verkettung maximal bis zu einer Tiefe von drei Ebenen durchführen sollte, da sonst die Übersichtlichkeit verloren ginge.
Arten
Damit kommt man auf verschiedene Arten von Verkettungsmöglichkeiten und wir wollen dir hier einmal ein paar Verschiedene zeigen. Die Liste ist nicht vollständig und der Fantasie sind keine Grenzen gesetzt:
" 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( ).
Dazu noch eine kurze Erklärung der Sachverhalte:
- Erzeugung der Instanz direkt bei Übergabe an die Methode und damit weglassen der Zwischenvariablen.
- Verwendung des Return-Parameters um mit einem Loop über das Ergebnis zu iterieren.
- Verwendung in einer Abfrage und Validierung des Return-Parameters. In diesem Fall kann auch der Vergleich gegen ABAP_TRUE weggelassen werden. Mehr dazu in diesem Artikel.
- Erzeugung der Instanz und Aufruf der Methode, die Referenz wird nicht weiter benötigt.
Casting
Bei Casting sprechen wir eigentlich nur von der Zuweisung einer Instanz auf eine Referenz die nicht den gleichen Typen wie die Instanz hat, die beiden Objekte aber durch ein Interface oder über Vererbung miteinander bekannt sind. Nehmen wir dazu das folgende Klassenmodell, welches wir bereits in diesem Artikel herangezogen haben:
Dazu legen wir nun eine Referenzvariable an, die auf die Oberklasse LCL_VEHICLE verweist. Im ersten Beispiel legen wir eine neue Instanz des Objekts an und können direkt die Methode SPEED_UP aufrufen.
DATA:
go_vehicle TYPE REF TO lcl_vehicle.
go_vehicle = NEW #( ).
go_vehicle->speed_up( ).
Im nächsten Schritt erzeugen wir eine neue Instanz vom Typ LCL_CAR, also einer spezielleren Variante von LCL_VEHICLE und weisen das Objekt unserer Referenz zu. Die Zuweisung klappt, da die Variable von einer gleichwertigen oder allgemeineren Ableitung der Klasse ist.
go_vehicle = NEW lcl_car( ).
go_vehicle->speed_up( ).
Jedoch kommen wir über die Referenzvariable nicht an die spezielle Methode der Klasse LCL_CAR, da die Variable auf LCL_VEHICLE verweist und damit nur die Methode SPEED_UP kennt. An dieser Stelle müsse wir zuerst einen CAST durchführen.
DATA(go_casted_car) = CAST lcl_car( go_vehicle ).
go_casted_car->open_luggage_space( ).
Nach dem CAST haben wir eine neue Referenzvariable vom Typ LCL_CAR über die wir dann die entsprechende Methode aufrufen können und so auch die Funktionen von LCL_CAR nutzen können. Dies ist ebenfalls mit einer Verkettung möglich.
CAST lcl_car( go_vehicle )->open_luggage_space( ).
Nehmen wir uns noch einmal das Beispiel vom Anfang und versuchen unsere Referenz vom Typ LCL_VEHICLE auf die Klasse LCL_CAR zu casten. In diesem Fall werden wir eine Ausnahme bekommen, da ein Cast der allgemeineren Instanz auf eine speziellere Instanz nicht möglich ist. Die speziellere Instanz hat spezifischere Ausprägungen und mehr Attribute, die unter Umständen noch nicht initialisiert sind.
TRY.
DATA(go_casted_vehicle) = CAST lcl_car( go_vehicle ).
go_casted_vehicle->open_luggage_space( ).
CATCH cx_sy_move_cast_error.
ENDTRY.
Das vollständige Beispiel aus diesem Kapitel sieht daher wie folgt aus:
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( ).
Fazit
Das Arbeiten mit Objekten und Instanzen kann auf den ersten Blick etwas verwirrend sein, folgt aber einer sehr genauen Logik. Mit der Vererbung kannst du auf sehr leichtem Weg Quellcode wiederverwenden, musst beim Zugriff allerdings auf die Spezialitäten achten.