ABAP INTERFACE: Schnittstellen für polymorphe Programmierung

kategorie
ABAP-Statements
Veröffentlicht
autor
Johannes

Ein INTERFACE in ABAP definiert einen Vertrag (Contract), den Klassen erfüllen müssen. Es legt fest, welche Methoden eine Klasse bereitstellen muss, ohne deren Implementierung vorzugeben.

Grundkonzept

  • Ein Interface definiert nur die Signaturen von Methoden (und optional Attribute/Konstanten).
  • Eine Klasse implementiert das Interface und liefert die konkrete Logik.
  • Polymorphismus: Verschiedene Klassen können das gleiche Interface implementieren, aber unterschiedlich reagieren.

Syntax

Interface definieren

INTERFACE <interfacename>.
METHODS: <methode1> [IMPORTING/EXPORTING/RETURNING ...],
<methode2> [...].
DATA: <attribut> TYPE <typ>.
CONSTANTS: <konstante> TYPE <typ> VALUE <wert>.
ENDINTERFACE.

Interface in Klasse implementieren

CLASS <klassenname> DEFINITION.
PUBLIC SECTION.
INTERFACES: <interfacename>.
" Optional: Alias für Interface-Methoden
ALIASES: <alias> FOR <interfacename>~<methode>.
ENDCLASS.
CLASS <klassenname> IMPLEMENTATION.
METHOD <interfacename>~<methode>.
" Implementierung
ENDMETHOD.
ENDCLASS.

Beispiele

1. Einfaches Interface

" Interface definieren
INTERFACE lif_printable.
METHODS: print RETURNING VALUE(rv_output) TYPE string.
ENDINTERFACE.
" Klasse 1: Dokument
CLASS lcl_document DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_printable.
METHODS: constructor IMPORTING iv_title TYPE string.
PRIVATE SECTION.
DATA: mv_title TYPE string.
ENDCLASS.
CLASS lcl_document IMPLEMENTATION.
METHOD constructor.
mv_title = iv_title.
ENDMETHOD.
METHOD lif_printable~print.
rv_output = |Dokument: { mv_title }|.
ENDMETHOD.
ENDCLASS.
" Klasse 2: Rechnung
CLASS lcl_invoice DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_printable.
METHODS: constructor IMPORTING iv_number TYPE string
iv_amount TYPE p.
PRIVATE SECTION.
DATA: mv_number TYPE string,
mv_amount TYPE p DECIMALS 2.
ENDCLASS.
CLASS lcl_invoice IMPLEMENTATION.
METHOD constructor.
mv_number = iv_number.
mv_amount = iv_amount.
ENDMETHOD.
METHOD lif_printable~print.
rv_output = |Rechnung { mv_number }: { mv_amount } EUR|.
ENDMETHOD.
ENDCLASS.
" Verwendung mit Polymorphismus
DATA: lt_printables TYPE TABLE OF REF TO lif_printable,
lo_printable TYPE REF TO lif_printable.
" Verschiedene Objekte in eine Liste
APPEND NEW lcl_document( 'Vertrag' ) TO lt_printables.
APPEND NEW lcl_invoice( iv_number = 'R-001' iv_amount = '1500.00' ) TO lt_printables.
" Alle gleich behandeln
LOOP AT lt_printables INTO lo_printable.
WRITE: / lo_printable->print( ).
ENDLOOP.
" Ausgabe:
" Dokument: Vertrag
" Rechnung R-001: 1500.00 EUR

2. Interface mit Alias

Mit ALIASES können Sie kürzere Namen für Interface-Methoden vergeben:

INTERFACE lif_calculator.
METHODS: calculate IMPORTING iv_a TYPE i
iv_b TYPE i
RETURNING VALUE(rv_result) TYPE i.
ENDINTERFACE.
CLASS lcl_adder DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_calculator.
" Alias für einfacheren Zugriff
ALIASES: add FOR lif_calculator~calculate.
ENDCLASS.
CLASS lcl_adder IMPLEMENTATION.
METHOD lif_calculator~calculate.
rv_result = iv_a + iv_b.
ENDMETHOD.
ENDCLASS.
" Verwendung
DATA: lo_adder TYPE REF TO lcl_adder.
lo_adder = NEW #( ).
" Mit vollständigem Namen
DATA(lv_result1) = lo_adder->lif_calculator~calculate( iv_a = 5 iv_b = 3 ).
" Mit Alias (kürzer)
DATA(lv_result2) = lo_adder->add( iv_a = 5 iv_b = 3 ).

3. Mehrere Interfaces implementieren

Eine Klasse kann mehrere Interfaces implementieren:

INTERFACE lif_serializable.
METHODS: to_json RETURNING VALUE(rv_json) TYPE string.
ENDINTERFACE.
INTERFACE lif_comparable.
METHODS: compare_to IMPORTING io_other TYPE REF TO lif_comparable
RETURNING VALUE(rv_result) TYPE i.
ENDINTERFACE.
CLASS lcl_product DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_serializable,
lif_comparable.
METHODS: constructor IMPORTING iv_id TYPE i
iv_name TYPE string
iv_price TYPE p.
DATA: mv_id TYPE i READ-ONLY,
mv_name TYPE string READ-ONLY,
mv_price TYPE p DECIMALS 2 READ-ONLY.
ENDCLASS.
CLASS lcl_product IMPLEMENTATION.
METHOD constructor.
mv_id = iv_id.
mv_name = iv_name.
mv_price = iv_price.
ENDMETHOD.
METHOD lif_serializable~to_json.
rv_json = |{ "id": { mv_id }, "name": "{ mv_name }", "price": { mv_price } }|.
ENDMETHOD.
METHOD lif_comparable~compare_to.
DATA: lo_other TYPE REF TO lcl_product.
lo_other ?= io_other.
IF mv_price < lo_other->mv_price.
rv_result = -1.
ELSEIF mv_price > lo_other->mv_price.
rv_result = 1.
ELSE.
rv_result = 0.
ENDIF.
ENDMETHOD.
ENDCLASS.

4. Interface mit Konstanten und Attributen

INTERFACE lif_status.
CONSTANTS: c_new TYPE i VALUE 1,
c_active TYPE i VALUE 2,
c_completed TYPE i VALUE 3,
c_cancelled TYPE i VALUE 4.
DATA: mv_current_status TYPE i.
METHODS: set_status IMPORTING iv_status TYPE i,
get_status RETURNING VALUE(rv_status) TYPE i.
ENDINTERFACE.
CLASS lcl_order DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_status.
ENDCLASS.
CLASS lcl_order IMPLEMENTATION.
METHOD lif_status~set_status.
lif_status~mv_current_status = iv_status.
ENDMETHOD.
METHOD lif_status~get_status.
rv_status = lif_status~mv_current_status.
ENDMETHOD.
ENDCLASS.
" Verwendung der Interface-Konstanten
DATA: lo_order TYPE REF TO lcl_order.
lo_order = NEW #( ).
lo_order->lif_status~set_status( lif_status=>c_active ).

5. Interface-Vererbung (Composed Interface)

Interfaces können andere Interfaces einschließen:

INTERFACE lif_readable.
METHODS: read RETURNING VALUE(rv_data) TYPE string.
ENDINTERFACE.
INTERFACE lif_writable.
METHODS: write IMPORTING iv_data TYPE string.
ENDINTERFACE.
" Zusammengesetztes Interface
INTERFACE lif_read_write.
INTERFACES: lif_readable,
lif_writable.
METHODS: clear.
ENDINTERFACE.
" Eine Klasse, die lif_read_write implementiert,
" muss alle Methoden aus allen drei Interfaces implementieren
CLASS lcl_buffer DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_read_write.
PRIVATE SECTION.
DATA: mv_buffer TYPE string.
ENDCLASS.
CLASS lcl_buffer IMPLEMENTATION.
METHOD lif_readable~read.
rv_data = mv_buffer.
ENDMETHOD.
METHOD lif_writable~write.
mv_buffer = iv_data.
ENDMETHOD.
METHOD lif_read_write~clear.
CLEAR mv_buffer.
ENDMETHOD.
ENDCLASS.

6. Dependency Injection mit Interfaces

Interfaces ermöglichen lose Kopplung und bessere Testbarkeit:

" Interface für Datenbankzugriff
INTERFACE lif_customer_repository.
METHODS: get_customer IMPORTING iv_id TYPE i
RETURNING VALUE(rs_customer) TYPE zcustomer
RAISING cx_not_found.
ENDINTERFACE.
" Produktive Implementierung
CLASS lcl_db_customer_repository DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_customer_repository.
ENDCLASS.
CLASS lcl_db_customer_repository IMPLEMENTATION.
METHOD lif_customer_repository~get_customer.
SELECT SINGLE * FROM zcustomer
WHERE id = @iv_id
INTO @rs_customer.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_not_found.
ENDIF.
ENDMETHOD.
ENDCLASS.
" Mock für Unit Tests
CLASS lcl_mock_customer_repository DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_customer_repository.
ENDCLASS.
CLASS lcl_mock_customer_repository IMPLEMENTATION.
METHOD lif_customer_repository~get_customer.
rs_customer = VALUE #( id = iv_id name = 'Test Customer' ).
ENDMETHOD.
ENDCLASS.
" Service nutzt das Interface (nicht die konkrete Klasse)
CLASS lcl_customer_service DEFINITION.
PUBLIC SECTION.
METHODS: constructor IMPORTING io_repository TYPE REF TO lif_customer_repository,
get_customer_name IMPORTING iv_id TYPE i
RETURNING VALUE(rv_name) TYPE string.
PRIVATE SECTION.
DATA: mo_repository TYPE REF TO lif_customer_repository.
ENDCLASS.
CLASS lcl_customer_service IMPLEMENTATION.
METHOD constructor.
mo_repository = io_repository.
ENDMETHOD.
METHOD get_customer_name.
TRY.
DATA(ls_customer) = mo_repository->get_customer( iv_id ).
rv_name = ls_customer-name.
CATCH cx_not_found.
rv_name = 'Unbekannt'.
ENDTRY.
ENDMETHOD.
ENDCLASS.

Interface vs. Abstrakte Klasse

AspektInterfaceAbstrakte Klasse
MehrfachvererbungJa (mehrere Interfaces)Nein (nur eine Elternklasse)
ImplementierungKeineKann teilweise implementieren
AttributeJa (aber unüblich)Ja
KonstruktorNeinJa
AnwendungVerträge, PolymorphismusGemeinsame Basislogik

Typprüfung und Casting

DATA: lo_object TYPE REF TO object,
lo_printable TYPE REF TO lif_printable.
lo_object = NEW lcl_document( 'Test' ).
" Prüfen, ob Interface implementiert wird
IF lo_object IS INSTANCE OF lif_printable.
" Downcast zum Interface
lo_printable ?= lo_object.
WRITE: / lo_printable->print( ).
ENDIF.

Wichtige Hinweise / Best Practice

  • Interfaces beginnen oft mit lif_ (lokal) oder zif_ / if_ (global).
  • Nutzen Sie Interfaces für lose Kopplung und bessere Testbarkeit.
  • Bevorzugen Sie Interfaces über Vererbung für Polymorphismus.
  • Halten Sie Interfaces klein und fokussiert (Interface Segregation Principle).
  • Verwenden Sie ALIASES für häufig genutzte Interface-Methoden.
  • Interface-Konstanten sind nützlich für Status-Codes und Enumerationen.
  • Kombinieren Sie Interfaces mit TRY...CATCH für robuste Fehlerbehandlung.
  • Nutzen Sie CLASS für die Implementierung von Interfaces.