ABAP OO - Interface und abstrakte Klasse
Wir hatten dir bereits die Vererbung in ABAP OO näher gebracht und schauen uns nun Interfaces, sowie abstrakte Klassen an und wie sie dir bei der Modellierung helfen.
Inhaltsverzeichnis
In einem der letzten Artikel zu ABAP OO sind wir etwas mehr auf die Vererbung eingegangen und wie sie dir bei der Wiederverwendung und Kapselung von Objekten helfen kann. Heute schauen wir uns die Baupläne in der objektorientierten Programmierung an und was du damit alles tun kannst.
Bauplan
In der Welt der Objekte wirst du immer wieder an Stellen kommen, da möchtest du flexible Objekte verwenden die bestimmte Methoden wiederverwenden und bestimmte Attribute haben und sich doch unterschiedlich verhalten sollen können. Für solche Objekte benötigst du einen gleichen "Bauplan" und individuelle Ausprägungen.
Dies kannst du auch mit einer einfachen Klasse realisieren von der du erbst und deren Methoden du entsprechend redefinierst und anders ausprägst. Die ursprüngliche Klasse kann dann in Schnittstellen verwendet werden, aber auch direkt instanziiert und verwendet werden. Dabei kann die Nutzung als Bauplan aber leicht übersehen werden, da es sich bereits um eine fertige Klasse handelt.
Interface
Ein Interface ist ein einfacher Bauplan der die öffentliche Schnittstelle einer Klasse repräsentiert. In einem Interface kannst du folgende Objekte im PUBLIC Bereich definieren:
- Typen
- Variablen
- Methodendefinitionen
Definieren wir dazu einmal ein einfaches Interface, das alle Typen enthält. Zusätzlich enthält das Interface eine Methode die einen neuen Ergebnissatz anlegen soll und diesen im Puffer der späteren Klasse hält:
INTERFACE zif_bs_demo_interface PUBLIC.
TYPES:
BEGIN OF ts_result,
identifier TYPE sysuuid_x16,
subrc TYPE sy-subrc,
name TYPE string,
END OF ts_result,
tt_result TYPE SORTED TABLE OF ts_result WITH UNIQUE KEY identifier.
DATA:
gt_buffer TYPE tt_result.
METHODS:
create_result
IMPORTING
id_name TYPE string
RETURNING VALUE(rs_result) TYPE ts_result.
ENDINTERFACE.
Wenn eine Klasse dann dieses Interface implementiert, müssen wir die entsprechende Methode definieren, ansonsten können wir die Klasse nicht aktivieren. Die Implementierung der Klasse könnte wie folgt aussehen:
CLASS zcl_bs_demo_interface DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_bs_demo_interface.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_bs_demo_interface IMPLEMENTATION.
METHOD zif_bs_demo_interface~create_result.
rs_result = VALUE #(
identifier = cl_system_uuid=>create_uuid_x16_static( )
subrc = 0
name = id_name
).
INSERT rs_result INTO TABLE zif_bs_demo_interface~gt_buffer.
ENDMETHOD.
ENDCLASS.
Wie du siehst mussten wir nur noch die Methode programmieren und bekommen den Typen, sowie die globale Variable geschenkt. Dies funktioniert genau so für große Interfaces. Dabei solltest du allerdings daran denken, jede neue Klasse die das Interface verwendet, muss alle Methoden implementieren, auch wenn diese vielleicht keinen Inhalt haben. Wie verwendest du nun am besten das Interface und übergibst es in den einzelnen Schnittstellen? Dazu haben wir für dich eine kleine Klasse mit Verwendung angelegt:
CLASS zcl_bs_demo_interface_usage DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
output_buffer
IMPORTING
io_demo TYPE REF TO zif_bs_demo_interface
io_out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.
CLASS zcl_bs_demo_interface_usage IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA:
lo_interface TYPE REF TO zif_bs_demo_interface,
lo_class TYPE REF TO zcl_bs_demo_interface.
lo_interface = NEW zcl_bs_demo_interface( ).
lo_interface->create_result( `John Doe` ).
output_buffer( io_demo = lo_interface io_out = out ).
lo_class = NEW #( ).
lo_class->zif_bs_demo_interface~create_result( `Jane Doe` ).
output_buffer( io_demo = lo_class io_out = out ).
ENDMETHOD.
METHOD output_buffer.
io_out->write( io_demo->gt_buffer ).
ENDMETHOD.
ENDCLASS.
In dem Beispiel definieren wir eine Methode, um den Puffer der Klasse auszugeben und damit die Verwendung der Referenz zu simulieren, in dem wir sie an die Methode übergeben. Dabei verwenden wir direkt das Interface zur Typisierung, das hat zwei Vorteile bei der Nutzung. Erstens können wir unterschiedliche Klassen übergeben, die auf unserem Interface basieren. Zweitens haben wir nur die gemeinsamen Attribute und Methoden im Zugriff und damit ist eine einheitliche Handhabung gewährleistet.
Bei der Erzeugung der Instanz in der MAIN Methode klappt die Übergabe auf Basis des Interfaces und der konkreten Implementierung der Klasse, basis ist hier die Implementierung des gemeinsamen Interfaces. Allerdings wird dir auffallen, dass es Unterschiede bei der Erzeugung der Instanz und beim Aufruf der Methode gibt. So haben beide Varianten Vor- und Nachteile bei der Verwendung. Empfohlen wird das einheitliche Arbeiten über eine Instanz basierend auf dem Interface.
Abstrakte Klasse
Was macht also die abstrakte Klasse so speziell und wie kann sie uns als Bausplan dienen? Bei der abstrakten Klasse handelt es sich um eine echte Klasse, kann somit auch Methodenimplementierungen haben. Es können aber ebenso Methodendefinitionen angelegt werden, die erst einmal keine Implementierung haben. Solche Klassen besitzen das Schlüsselwort ABSTRACT und können damit nicht mehr direkt instanziiert werden. Methoden die als Hüllen dienen und später implementiert werden sollen, werden ebenfalls mit dem Schlüsselwort ABSTRACT gekennzeichnet.
Als Beispiel haben wir einmal die Definition und die Logik des Interface Beispiels wiederverwendet. Die Implementierung befindet sich direkt in der abstrakten Klasse. Weiterhin definieren wir noch eine abstrakte Methode, die wir dann in der erbenden Klasse implementieren wollen.
CLASS zcl_bs_demo_abstract DEFINITION PUBLIC ABSTRACT CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ts_result,
identifier TYPE sysuuid_x16,
subrc TYPE sy-subrc,
name TYPE string,
END OF ts_result,
tt_result TYPE SORTED TABLE OF ts_result WITH UNIQUE KEY identifier.
DATA:
gt_buffer TYPE tt_result.
METHODS:
create_result
IMPORTING
id_name TYPE string
RETURNING VALUE(rs_result) TYPE ts_result,
create_empty_entry ABSTRACT.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_bs_demo_abstract IMPLEMENTATION.
METHOD create_result.
rs_result = VALUE #(
identifier = cl_system_uuid=>create_uuid_x16_static( )
subrc = 0
name = id_name
).
INSERT rs_result INTO TABLE gt_buffer.
ENDMETHOD.
ENDCLASS.
Damit wir die abstrakte Klasse auch verwenden und instanziieren können, müssen wir eine implementierende Klasse anlegen.
CLASS zcl_bs_demo_abstract_impl DEFINITION PUBLIC FINAL CREATE PUBLIC
INHERITING FROM zcl_bs_demo_abstract.
PUBLIC SECTION.
METHODS:
create_empty_entry REDEFINITION.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_bs_demo_abstract_impl IMPLEMENTATION.
METHOD create_empty_entry.
ENDMETHOD.
ENDCLASS.
Wie du siehst ist die Implementierung sehr schlank gehalten und nur die abstrakte Methode muss redefiniert und implementiert werden. An dieser Stelle wollten wir dir noch einmal zeigen, dass nicht die komplette Logik in der abstrakten Klasse enthalten sein muss, sondern auch einzelne Methoden in der erbenden Klasse definiert werden können.
Bei der Nutzung der abstrakten Klasse sieht es nun soweit ähnlich aus wie beim Interface, die Instanziierung kann beide Arten von Objekten verwenden. Es wird aber auch hier empfohlen den abstraktesten Teil zu verwenden, damit die Schnittstelle in jedem Fall funktioniert und auch andere Implementierungen der abstrakten Klasse verwendet werden können.
CLASS zcl_bs_demo_abstract_usage DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
output_buffer
IMPORTING
io_demo TYPE REF TO zcl_bs_demo_abstract
io_out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.
CLASS zcl_bs_demo_abstract_usage IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA:
lo_abstract TYPE REF TO zcl_bs_demo_abstract,
lo_class TYPE REF TO zcl_bs_demo_abstract_impl.
lo_abstract = NEW zcl_bs_demo_abstract_impl( ).
lo_abstract->create_result( `John Doe` ).
output_buffer( io_demo = lo_abstract io_out = out ).
lo_class = NEW #( ).
lo_class->create_result( `Jane Doe` ).
output_buffer( io_demo = lo_class io_out = out ).
ENDMETHOD.
METHOD output_buffer.
io_out->write( io_demo->gt_buffer ).
ENDMETHOD.
ENDCLASS.
Nutzung
Wann sollte man also welchen Bauplan verwenden? Interfaces und abstrakte Klassen haben beide ihre Stärken und Schwächen, deshalb wollen wir in diesem Abschnitt noch einmal auf die Unterschiede eingehen und Tipps geben. Eine Instanziierung von Interfaces oder abstrakten Klassen ist nicht direkt möglich und benötigt immer eine erbende Klasse.
Interface
In der Theorie solltest du immer erst einmal mit einem Interface starten, da es leicht verständlich ist und sehr gut mit dem Test Double Framework funktioniert, wenn du ABAP Unit einsetzt. In den Schnittstellen ist von vornherein klar, dass hier eine konkrete Klasse gefordert wird, um den Parameter zu versorgen. Das Interface ist von der Definition ebenfalls sehr leicht und benötigt keinen Implementierungsteil.
Abstrakte Klasse
Sobald du Coding hast, dass du wiederverwenden willst, fährst du am Besten mit einer abstrakten Klasse, da du der erbenden Klasse Implementierungen vorgeben kannst. Damit bist du aber auch so flexibel, um solche Vorgaben per Redefinition zu überschreiben. Das Test Double Framework funktioniert nicht mit abstrakten Klassen, hier musst du andere Methoden einsetzen, um dein Coding testbar zu machen.
Zusammengesetzte Interfaces
Du kannst verschiedene Interfaces auch zu einem größeren Interface kombinieren, wie du dies tust, findest du in der offiziellen ABAP Dokumentation von SAP. Die Kombination und Verwendung mehrere Interfaces funktioniert, ist aber nicht ganz so handlich. Aus unser persönlichen Erfahrung empfehlen wir diese Methode nur bedingt.
Fazit
In diesem Artikel hast du mehr über die Verwendung von Interfaces und abstrakten Klassen erfahren und wann du diese nutzen solltest. Versuche es immer erst mit einem Interface und wenn du Coding wiederverwenden willst, wechsle dann auf das Konzept der abstrakten Klasse.