ABAP Vererbung: INHERITING FROM, ABSTRACT und FINAL

kategorie
ABAP-Statements
Veröffentlicht
autor
Johannes

Vererbung ist ein zentrales OOP-Konzept, das die Wiederverwendung und Erweiterung von Klassen ermöglicht. In ABAP wird Vererbung mit INHERITING FROM realisiert. ABSTRACT und FINAL steuern die Erweiterbarkeit.

Grundkonzepte

  • Vererbung: Eine Klasse (Unterklasse) übernimmt Eigenschaften einer anderen (Basisklasse)
  • ABSTRACT: Klassen/Methoden, die nicht direkt instanziiert/aufgerufen werden können
  • FINAL: Klassen/Methoden, die nicht weiter vererbt/überschrieben werden können
  • REDEFINITION: Methoden der Basisklasse in Unterklasse überschreiben

Syntax

Vererbung

CLASS <unterklasse> DEFINITION
INHERITING FROM <basisklasse>.

Abstract

CLASS <klassenname> DEFINITION ABSTRACT.
METHODS: <methodenname> ABSTRACT.
ENDCLASS.

Final

CLASS <klassenname> DEFINITION FINAL.
METHODS: <methodenname> FINAL.
ENDCLASS.

Redefinition

CLASS <unterklasse> DEFINITION INHERITING FROM <basisklasse>.
METHODS: <methodenname> REDEFINITION.
ENDCLASS.

Beispiele

1. Einfache Vererbung

" Basisklasse
CLASS lcl_vehicle DEFINITION.
PUBLIC SECTION.
METHODS: constructor IMPORTING iv_brand TYPE string,
get_brand RETURNING VALUE(rv_brand) TYPE string,
start.
PROTECTED SECTION.
DATA: mv_brand TYPE string.
ENDCLASS.
CLASS lcl_vehicle IMPLEMENTATION.
METHOD constructor.
mv_brand = iv_brand.
ENDMETHOD.
METHOD get_brand.
rv_brand = mv_brand.
ENDMETHOD.
METHOD start.
WRITE: / 'Fahrzeug startet...'.
ENDMETHOD.
ENDCLASS.
" Unterklasse
CLASS lcl_car DEFINITION INHERITING FROM lcl_vehicle.
PUBLIC SECTION.
METHODS: constructor IMPORTING iv_brand TYPE string
iv_doors TYPE i,
get_doors RETURNING VALUE(rv_doors) TYPE i.
PRIVATE SECTION.
DATA: mv_doors TYPE i.
ENDCLASS.
CLASS lcl_car IMPLEMENTATION.
METHOD constructor.
" Basisklassen-Konstruktor aufrufen
super->constructor( iv_brand ).
mv_doors = iv_doors.
ENDMETHOD.
METHOD get_doors.
rv_doors = mv_doors.
ENDMETHOD.
ENDCLASS.
" Verwendung
DATA: lo_car TYPE REF TO lcl_car.
lo_car = NEW #( iv_brand = 'BMW' iv_doors = 4 ).
WRITE: / lo_car->get_brand( ). " BMW (geerbt)
WRITE: / lo_car->get_doors( ). " 4 (eigene Methode)
lo_car->start( ). " Fahrzeug startet... (geerbt)

2. Methoden überschreiben (REDEFINITION)

CLASS lcl_car DEFINITION INHERITING FROM lcl_vehicle.
PUBLIC SECTION.
METHODS: constructor IMPORTING iv_brand TYPE string
iv_doors TYPE i,
start REDEFINITION. " Methode überschreiben
PRIVATE SECTION.
DATA: mv_doors TYPE i.
ENDCLASS.
CLASS lcl_car IMPLEMENTATION.
METHOD constructor.
super->constructor( iv_brand ).
mv_doors = iv_doors.
ENDMETHOD.
METHOD start.
" Eigene Implementierung
WRITE: / 'Auto startet den Motor...'.
" Optional: Basisklassen-Methode aufrufen
" super->start( ).
ENDMETHOD.
ENDCLASS.
" Verwendung
DATA: lo_vehicle TYPE REF TO lcl_vehicle,
lo_car TYPE REF TO lcl_car.
lo_vehicle = NEW lcl_vehicle( 'Generic' ).
lo_vehicle->start( ). " Fahrzeug startet...
lo_car = NEW lcl_car( iv_brand = 'BMW' iv_doors = 4 ).
lo_car->start( ). " Auto startet den Motor...

3. SUPER – Basisklasse aufrufen

CLASS lcl_electric_car DEFINITION INHERITING FROM lcl_car.
PUBLIC SECTION.
METHODS: constructor IMPORTING iv_brand TYPE string
iv_doors TYPE i
iv_battery TYPE i,
start REDEFINITION.
PRIVATE SECTION.
DATA: mv_battery_capacity TYPE i.
ENDCLASS.
CLASS lcl_electric_car IMPLEMENTATION.
METHOD constructor.
" Elternklassen-Konstruktor aufrufen
super->constructor(
iv_brand = iv_brand
iv_doors = iv_doors
).
mv_battery_capacity = iv_battery.
ENDMETHOD.
METHOD start.
WRITE: / 'Batterie-Check...'.
WRITE: / |Kapazität: { mv_battery_capacity } kWh|.
" Methode der Basisklasse aufrufen
super->start( ).
ENDMETHOD.
ENDCLASS.
" Verwendung
DATA(lo_tesla) = NEW lcl_electric_car(
iv_brand = 'Tesla'
iv_doors = 4
iv_battery = 100
).
lo_tesla->start( ).
" Ausgabe:
" Batterie-Check...
" Kapazität: 100 kWh
" Auto startet den Motor...

4. Abstrakte Klassen

" Abstrakte Klasse - kann nicht instanziiert werden
CLASS lcl_shape DEFINITION ABSTRACT.
PUBLIC SECTION.
" Abstrakte Methode - MUSS in Unterklasse implementiert werden
METHODS: calculate_area ABSTRACT
RETURNING VALUE(rv_area) TYPE f.
" Konkrete Methode - kann geerbt werden
METHODS: get_name RETURNING VALUE(rv_name) TYPE string.
PROTECTED SECTION.
DATA: mv_name TYPE string.
ENDCLASS.
CLASS lcl_shape IMPLEMENTATION.
METHOD get_name.
rv_name = mv_name.
ENDMETHOD.
" calculate_area hat keine Implementierung (abstrakt)
ENDCLASS.
" Konkrete Unterklasse: Kreis
CLASS lcl_circle DEFINITION INHERITING FROM lcl_shape.
PUBLIC SECTION.
METHODS: constructor IMPORTING iv_radius TYPE f,
calculate_area REDEFINITION.
PRIVATE SECTION.
DATA: mv_radius TYPE f.
CONSTANTS: c_pi TYPE f VALUE '3.14159265'.
ENDCLASS.
CLASS lcl_circle IMPLEMENTATION.
METHOD constructor.
super->constructor( ).
mv_name = 'Kreis'.
mv_radius = iv_radius.
ENDMETHOD.
METHOD calculate_area.
rv_area = c_pi * mv_radius ** 2.
ENDMETHOD.
ENDCLASS.
" Konkrete Unterklasse: Rechteck
CLASS lcl_rectangle DEFINITION INHERITING FROM lcl_shape.
PUBLIC SECTION.
METHODS: constructor IMPORTING iv_width TYPE f
iv_height TYPE f,
calculate_area REDEFINITION.
PRIVATE SECTION.
DATA: mv_width TYPE f,
mv_height TYPE f.
ENDCLASS.
CLASS lcl_rectangle IMPLEMENTATION.
METHOD constructor.
super->constructor( ).
mv_name = 'Rechteck'.
mv_width = iv_width.
mv_height = iv_height.
ENDMETHOD.
METHOD calculate_area.
rv_area = mv_width * mv_height.
ENDMETHOD.
ENDCLASS.
" Verwendung
DATA: lt_shapes TYPE TABLE OF REF TO lcl_shape.
" lo_shape = NEW lcl_shape( ). " FEHLER! Abstrakt!
APPEND NEW lcl_circle( 5 ) TO lt_shapes.
APPEND NEW lcl_rectangle( iv_width = 4 iv_height = 3 ) TO lt_shapes.
LOOP AT lt_shapes INTO DATA(lo_shape).
WRITE: / lo_shape->get_name( ), ': Fläche =', lo_shape->calculate_area( ).
ENDLOOP.
" Ausgabe:
" Kreis: Fläche = 78.54
" Rechteck: Fläche = 12.00

5. FINAL – Vererbung verhindern

" Finale Klasse - kann NICHT vererbt werden
CLASS lcl_singleton DEFINITION FINAL.
PUBLIC SECTION.
CLASS-METHODS: get_instance
RETURNING VALUE(ro_instance) TYPE REF TO lcl_singleton.
PRIVATE SECTION.
CLASS-DATA: go_instance TYPE REF TO lcl_singleton.
METHODS: constructor.
ENDCLASS.
CLASS lcl_singleton IMPLEMENTATION.
METHOD constructor.
" Privater Konstruktor
ENDMETHOD.
METHOD get_instance.
IF go_instance IS NOT BOUND.
go_instance = NEW #( ).
ENDIF.
ro_instance = go_instance.
ENDMETHOD.
ENDCLASS.
" Diese Zeile würde Syntaxfehler verursachen:
" CLASS lcl_child DEFINITION INHERITING FROM lcl_singleton.
" ...
" ENDCLASS.

6. FINAL Methoden

CLASS lcl_base DEFINITION.
PUBLIC SECTION.
" Diese Methode kann NICHT überschrieben werden
METHODS: critical_operation FINAL.
" Diese Methode kann überschrieben werden
METHODS: normal_operation.
ENDCLASS.
CLASS lcl_base IMPLEMENTATION.
METHOD critical_operation.
WRITE: / 'Kritische Operation - nicht änderbar'.
ENDMETHOD.
METHOD normal_operation.
WRITE: / 'Normale Operation'.
ENDMETHOD.
ENDCLASS.
CLASS lcl_derived DEFINITION INHERITING FROM lcl_base.
PUBLIC SECTION.
" OK: normal_operation kann redefiniert werden
METHODS: normal_operation REDEFINITION.
" FEHLER: critical_operation ist FINAL
" METHODS: critical_operation REDEFINITION.
ENDCLASS.
CLASS lcl_derived IMPLEMENTATION.
METHOD normal_operation.
WRITE: / 'Überschriebene Operation'.
ENDMETHOD.
ENDCLASS.

7. PROTECTED – Zugriff für Unterklassen

CLASS lcl_account DEFINITION.
PUBLIC SECTION.
METHODS: constructor IMPORTING iv_balance TYPE p,
get_balance RETURNING VALUE(rv_balance) TYPE p,
deposit IMPORTING iv_amount TYPE p.
PROTECTED SECTION.
" Nur für diese Klasse und Unterklassen sichtbar
DATA: mv_balance TYPE p DECIMALS 2.
METHODS: validate_amount IMPORTING iv_amount TYPE p
RETURNING VALUE(rv_valid) TYPE abap_bool.
PRIVATE SECTION.
" Nur für diese Klasse sichtbar
DATA: mv_account_number TYPE string.
ENDCLASS.
CLASS lcl_account IMPLEMENTATION.
METHOD constructor.
mv_balance = iv_balance.
ENDMETHOD.
METHOD get_balance.
rv_balance = mv_balance.
ENDMETHOD.
METHOD deposit.
IF validate_amount( iv_amount ).
mv_balance = mv_balance + iv_amount.
ENDIF.
ENDMETHOD.
METHOD validate_amount.
rv_valid = xsdbool( iv_amount > 0 ).
ENDMETHOD.
ENDCLASS.
CLASS lcl_savings_account DEFINITION INHERITING FROM lcl_account.
PUBLIC SECTION.
METHODS: add_interest IMPORTING iv_rate TYPE p.
ENDCLASS.
CLASS lcl_savings_account IMPLEMENTATION.
METHOD add_interest.
" Zugriff auf PROTECTED mv_balance möglich
DATA(lv_interest) = mv_balance * iv_rate / 100.
" Zugriff auf PROTECTED validate_amount möglich
IF validate_amount( lv_interest ).
mv_balance = mv_balance + lv_interest.
ENDIF.
" FEHLER: mv_account_number ist PRIVATE
" mv_account_number = '123'.
ENDMETHOD.
ENDCLASS.

8. Polymorphismus

" Verschiedene Fahrzeugtypen
CLASS lcl_motorcycle DEFINITION INHERITING FROM lcl_vehicle.
PUBLIC SECTION.
METHODS: start REDEFINITION.
ENDCLASS.
CLASS lcl_motorcycle IMPLEMENTATION.
METHOD start.
WRITE: / 'Motorrad brummt los...'.
ENDMETHOD.
ENDCLASS.
CLASS lcl_truck DEFINITION INHERITING FROM lcl_vehicle.
PUBLIC SECTION.
METHODS: start REDEFINITION.
ENDCLASS.
CLASS lcl_truck IMPLEMENTATION.
METHOD start.
WRITE: / 'LKW-Diesel startet...'.
ENDMETHOD.
ENDCLASS.
" Polymorphe Verwendung
DATA: lt_fleet TYPE TABLE OF REF TO lcl_vehicle.
APPEND NEW lcl_car( iv_brand = 'BMW' iv_doors = 4 ) TO lt_fleet.
APPEND NEW lcl_motorcycle( 'Honda' ) TO lt_fleet.
APPEND NEW lcl_truck( 'MAN' ) TO lt_fleet.
" Alle Fahrzeuge starten - jedes auf seine Weise
LOOP AT lt_fleet INTO DATA(lo_vehicle).
lo_vehicle->start( ).
ENDLOOP.
" Ausgabe:
" Auto startet den Motor...
" Motorrad brummt los...
" LKW-Diesel startet...

9. Typprüfung mit IS INSTANCE OF

DATA: lo_vehicle TYPE REF TO lcl_vehicle.
lo_vehicle = NEW lcl_car( iv_brand = 'Audi' iv_doors = 4 ).
" Typprüfung
IF lo_vehicle IS INSTANCE OF lcl_car.
WRITE: / 'Ist ein Auto'.
" Downcast für spezifische Methoden
DATA(lo_car) = CAST lcl_car( lo_vehicle ).
WRITE: / 'Türen:', lo_car->get_doors( ).
ENDIF.
" Mit CASE TYPE OF
CASE TYPE OF lo_vehicle.
WHEN TYPE lcl_car.
WRITE: / 'Auto erkannt'.
WHEN TYPE lcl_motorcycle.
WRITE: / 'Motorrad erkannt'.
WHEN TYPE lcl_truck.
WRITE: / 'LKW erkannt'.
WHEN OTHERS.
WRITE: / 'Unbekannter Fahrzeugtyp'.
ENDCASE.

10. Abstrakte Klasse mit Template Method Pattern

CLASS lcl_report DEFINITION ABSTRACT.
PUBLIC SECTION.
" Template Method - definiert den Ablauf
METHODS: execute FINAL.
PROTECTED SECTION.
" Hook Methods - von Unterklassen zu implementieren
METHODS: fetch_data ABSTRACT,
process_data ABSTRACT,
display_output ABSTRACT.
ENDCLASS.
CLASS lcl_report IMPLEMENTATION.
METHOD execute.
" Fester Ablauf, nicht überschreibbar (FINAL)
WRITE: / 'Report startet...'.
fetch_data( ).
process_data( ).
display_output( ).
WRITE: / 'Report beendet.'.
ENDMETHOD.
ENDCLASS.
CLASS lcl_sales_report DEFINITION INHERITING FROM lcl_report.
PROTECTED SECTION.
METHODS: fetch_data REDEFINITION,
process_data REDEFINITION,
display_output REDEFINITION.
ENDCLASS.
CLASS lcl_sales_report IMPLEMENTATION.
METHOD fetch_data.
WRITE: / ' Lade Verkaufsdaten...'.
ENDMETHOD.
METHOD process_data.
WRITE: / ' Berechne Umsätze...'.
ENDMETHOD.
METHOD display_output.
WRITE: / ' Zeige Verkaufsreport an.'.
ENDMETHOD.
ENDCLASS.
" Verwendung
DATA(lo_report) = NEW lcl_sales_report( ).
lo_report->execute( ).
" Ausgabe:
" Report startet...
" Lade Verkaufsdaten...
" Berechne Umsätze...
" Zeige Verkaufsreport an.
" Report beendet.

Vererbungshierarchie

lcl_vehicle (Basisklasse)
├── lcl_car
│ │
│ └── lcl_electric_car
├── lcl_motorcycle
└── lcl_truck

Zusammenfassung

KonzeptSchlüsselwortBeschreibung
VererbungINHERITING FROMKlasse erweitert andere Klasse
ÜberschreibenREDEFINITIONMethode der Basisklasse neu implementieren
Basisklasse aufrufensuper->Zugriff auf Elternklasse
AbstraktABSTRACTNicht instanziierbar / muss implementiert werden
FinalFINALNicht vererbbar / nicht überschreibbar
GeschütztPROTECTEDSichtbar für Klasse und Unterklassen

Wichtige Hinweise / Best Practice

  • Vererbung für “ist-ein”-Beziehungen (Auto ist ein Fahrzeug).
  • Für “hat-ein”-Beziehungen besser Komposition verwenden.
  • PROTECTED für Attribute/Methoden, die Unterklassen brauchen.
  • ABSTRACT für Basisklassen, die nur als Vorlage dienen.
  • FINAL für Klassen/Methoden, die nicht verändert werden sollen.
  • Polymorphismus ermöglicht einheitliche Behandlung verschiedener Typen.
  • super-> zum Aufrufen der Basisklassen-Implementierung.
  • Prüfen Sie Typen mit IS INSTANCE OF vor dem CAST.
  • Bevorzugen Sie INTERFACE für lose Kopplung.
  • Tiefe Vererbungshierarchien vermeiden (max. 2-3 Ebenen).