SAP NetWeaver AS ABAP Release 750, ©Copyright 2016 SAP AG. Alle Rechte vorbehalten.
ABAP - Schlüsselwortdokumentation →
ABAP - Programmierrichtlinien →
ABAP-spezifische Grundregeln →
ABAP Objects als Programmiermodell
Hintergrund
ABAP ist eine hybride Programmiersprache, die sowohl ein prozedurales als auch ein objektorientiertes
Programmiermodell unterstützt. Das prozedurale Programmiermodell beruht auf der Modularisierung
von Programmen in die klassischen Verarbeitungsblöcke, das heißt Ereignisblöcke,
Dialogmodule, Funktionsbausteine und Unterprogramme. In ABAP Objects tritt die Klasse konzeptionell
an die Stelle des klassischen Programms, und die Modularisierung erfolgt durch deren Methoden. (Technisch gesehen, sind Klassen nach wie vor in Programmen deklariert und implementiert.)
Beide Modelle sind in der Weise interoperabel, dass in klassischen Verarbeitungsblöcken auf Klassen
zugegriffen werden kann und innerhalb von Methoden wiederum klassische Programme und Prozeduren aufgerufen
werden können. Der hybride Charakter der Sprache ist in erster Linie der Abwärtskompatibilität
geschuldet, da ABAP prozedurale Wurzeln hat und sowohl ganze Programme als auch wiederverwendbare Prozeduren
(in erster Linie Funktionsbausteine) mit Einführung des objektorientierten Programmiermodells weiterhin nutzbar bleiben sollten.
Regel
ABAP Objects verwenden
Verwenden Sie bei der Neu- und Weiterentwicklung so weit wie möglich ABAP Objects. Klassische Verarbeitungsblöcke dürfen nur noch in Ausnahmefällen neu angelegt werden.
Details
Die Forderung nach der Trennung
der Belange wird am besten durch eine weitestgehende Verwendung von ABAP Objects unterstützt.
Dass die objektorientierte Programmierung - und hier insbesondere ABAP Objects im Vergleich zum klassischen prozeduralen ABAP hat folgende Gründe:
-
Datenkapselung
ABAP Objects ermöglicht eine fortgeschrittene Art
der Datenkapselung. Bei der klassischen prozeduralen Programmierung wird der Zustand einer Anwendung
durch den Inhalt von globalen Variablen bestimmt. In der objektorientierten Programmierung ist der Zustand
in Klassen oder Objekten als Instanzen von Klassen gekapselt. Die Aufteilung der Daten in die verschiedenen
Sichtbarkeitsbereiche einer Klasse - öffentlich, geschützt und privat - sorgt für
eine klare Unterscheidung zwischen extern und intern verwendbaren Daten. Selbst ohne eine tief gehende
objektorientierte Modellierung profitieren Anwendungsprogramme hinsichtlich Robustheit und Wartbarkeit von diesen Eigenschaften.
-
Explizite Instanzierung
ABAP Objects ermöglicht die mehrfache Instanzierung
einer Klasse über explizite Objekterzeugung mittels der Anweisung CREATE
OBJECT. Jede Instanz einer Klasse (Objekt) hat einen eigenen Zustand, der durch die Werte ihrer
Attribute festgelegt wird und über die Methoden der Klassen geändert werden kann. Eine
automatische Garbage Collection sorgt dafür, dass Objekte, die nicht mehr benötigt werden,
aus dem Speicher gelöscht werden. Im prozeduralen Modell gibt es keine Mehrfachinstanzierung, weshalb dort mit zustandslosen Funktionen auf getrennt abgelegten Daten gearbeitet werden muss.
-
Vererbung
ABAP Objects ermöglicht die Wiederverwendung von Klassen
durch Vererbung, wobei Klassen mit speziellen Verhaltensweisen von allgemeineren Klassen abgeleitet
werden und nur die Unterschiede neu implementiert werden müssen. Im prozeduralen Modell können vorhandene Funktionen nur genauso verwendet werden, wie sie sind, oder es müssen neue angelegt werden.
-
Interfaces
In ABAP Objects können Objekte über eigenständige
Interfaces angesprochen werden. Dies befreit Entwickler davon, sich um Implementierungsdetails der hinter
dem Interface liegenden Klasse kümmern zu müssen. Dadurch kann der Anbieter eines Interface
die dahinterliegenden Implementierungen ändern, ohne dass die Programme, die das Interface verwenden,
modifiziert werden müssen. Im prozeduralen Modell gibt es kein solches Konzept eigenständiger Interfaces.
-
Ereignisse
ABAP Objects erleichtert die Implementierung ereignisgetriebener
Programmabläufe. Anwendungen können über einen Publish-and-Subscribe-Mechanismus
lose gekoppelt werden, wobei der Auslöser eines Ereignisses nichts über eventuelle Behandler
wissen muss. Dies erlaubt größere Flexibilität im Vergleich zum prozeduralen Ansatz, bei dem Programme stärker gekoppelt sind und der Programmablauf in der Regel viel starrer vorgegeben ist.
-
Explizite orthogonale Konzepte
In ABAP Objects gibt es eine kleine Anzahl
genau definierter fundamentaler und zueinander orthogonaler Konzepte, die es zuverlässiger und
weniger fehleranfällig als das klassische ABAP machen. Im klassischen prozeduralen ABAP dominieren
implizite Verhaltensweisen, in denen Programme durch implizite Ereignisse der Laufzeitumgebung und über
globale Daten gesteuert werden. Die Konzepte von ABAP Objects werden dagegen in einem Programm explizit
wiedergegeben. ABAP Objects ist damit im Vergleich zum klassischen prozeduralen ABAP einfacher erlern- und anwendbar.
-
Bereinigte Syntax
In ABAP Objects gelten bereinigte Syntax- und Semantikregeln.
Das klassische prozedurale ABAP ist eine evolutionär gewachsene Sprache mit vielen obsoleten
und sich überschneidenden Konzepten. Mit Einführung von ABAP Objects bot sich mit Klassen
und Methoden ein Feld für bereinigte Syntax- und Semantikregeln, das von Anforderungen an die
Abwärtskompatibilität völlig unbelastet war. Auf diese Weise konnten in ABAP Objects,
das heißt innerhalb von Klassen und Methoden, die meisten obsoleten und fehleranfälligen
Sprachkonstrukte syntaktisch verboten werden. Zusätzlich werden fragwürdige und potenziell
fehlerhafte Zugriffe auf Daten schärfer überprüft und gegebenenfalls ebenso verboten. Die Syntaxbereinigung erzwingt in Klassen eine Verwendung der Sprache ABAP, wie sie außerhalb von Klassen nur durch die
Richtlinie zu modernem ABAP gefordert werden kann.
-
Zugang zu neuen Technologien
ABAP Objects ist oft der einzige Weg, um
mit neuen ABAP-Technologien umzugehen. Beispielsweise bieten GUI Controls, Web Dynpro ABAP, Run Time
Type Services (RTTS) oder das Internet Connection Framework (ICF) ausschließlich klassenbasierte
Schnittstellen an. Wenn Programme, die solche Services verwenden, weiterhin rein prozedural implementiert
würden, käme es zu einer unnötigen Vermischung der Programmiermodelle mit entsprechender Erhöhung der Komplexität.
Die dringende Empfehlung zur Verwendung von ABAP Objects hat somit sowohl inhaltliche als auch formale Aspekte:
- Wie in den Punkten 1 bis 5 aufgeführt, ist das objektorientierte Programmiermodell inhaltlich
besser geeignet, die Komplexität von Software durch Prinzipien wie Kapselung und Vererbung beherrschbar
zu halten. Zugegebenermaßen ist gutes objektorientiertes Design keine leichte Aufgabe, und auch
heute noch gibt es Entwickler mit wenig Erfahrung auf diesem Gebiet. Wer vor diesem Hintergrund immer
noch mit dem Gedanken spielt, eine Neuentwicklung in klassischer prozeduraler Manier anzugehen, muss
sich jedoch vergegenwärtigen, dass auch das prozedurale ereignisgesteuerte ABAP-Programmiermodell mit seinen Systemereignissen nicht leicht zu durchschauen ist.
- Die Punkte 6 bis 8 beschreiben eher formale Aspekte. Die dort aufgeführten Gründe
sprechen dafür, Prozeduren heute nur noch in Form von Methoden anzulegen, selbst in Abwesenheit
eines echten objektorientierten Designs. Funktionsbausteine und Unterprogramme sollen nur noch in den Ausnahmefällen angelegt werden, in denen ABAP Objects bisher keine Alternative bietet.
Hinweise und Empfehlungen zum erfolgreichen Einsatz von ABAP Objects liefert der Abschnitt Objektorientierte Programmierung.
Ausnahme
Im derzeitigen Zustand fehlen in ABAP Objects noch folgende Eigenschaften, um klassische Verarbeitungsblöcke vollständig durch Methoden zu ersetzen:
- Remote Method Invocation (RMI) als Ersatz für den Remote Function Call (RFC)
- ein Ersatz für den Aufruf von Verbuchungsfunktionsbausteinen (CALL FUNCTION IN UPDATE TASK)
- ein Ersatz für den Aufruf von Unterprogrammen bei COMMIT WORK und ROLLBACK WORK (PERFORM ON COMMIT|ROLLBACK)
- objektorientierte Behandlung von klassischen Dynpros inklusive Selektionsbildern als Ersatz für
Dialogtransaktionen, CALL SCREEN und CALL SELECTION-SCREEN
- dynamische Erzeugung von Klassen als Ersatz für die klassische dynamische Programmerzeugung (GENERATE SUBROUTINE POOL)
- direkte Unterstützung der Hintergrundverarbeitung als Ersatz für den Aufruf
- ausführbarer Programme (SUBMIT VIA JOB)
Genau für diese Fälle dürfen in neuen Programmen noch folgende klassische Verarbeitungsblöcke angelegt werden:
- Unterprogramme werden noch für PERFORM ON COMMIT|ROLLBACK und in dynamisch generierten Subroutinen-Pools (GENERATE SUBROUTINE POOL) benötigt.
- Der Ereignisblock START-OF-SELECTION wird noch in ausführbaren Programmen benötigt, die für die Hintergrundverarbeitung vorgesehen sind.
Innerhalb eines solchen Verarbeitungsblocks soll die Ausführung dann jedoch sofort an eine geeignete
Methode delegiert werden. Diese muss keine Methode einer globalen Klasse sein, sondern kann durchaus
im Rahmen einer lokalen Klasse innerhalb des zugehörigen Rahmenprogramms angesiedelt sein. Damit auch in solchen Verarbeitungsblöcken die gleiche strengere Prüfung wie in Methoden durchgeführt wird, kann in der
erweiterten Programmprüfung die Prüfung Veraltete Anweisungen (OO-Kontext) eingeschaltet werden.
Schlechtes Beispiel
Folgende Quelltext enthält eine ansatzweise Implementierung der Behandlung von verschiedenen
Arten von Bankkonten in einer Funktionsgruppe und deren Verwendung in einem Programm, wobei nur die
Funktion "Abheben eines Betrags" gezeigt wird. Die Funktionsbausteine der Funktionsgruppe arbeiten auf
externen Daten, die hier beim Ereignis LOAD-OF-PROGRAM in eine globale interne
Tabelle geladen werden. Die Steuerung, ob mit einem Giro- oder Sparkonto umgegangen wird, erfolgt über
einen Eingabeparameter, und die unterschiedliche Behandlung wird über eine CASE-
WHEN-Kontrollstruktur an unterschiedliche Unterprogramme delegiert, wobei keine Wiederverwendung
stattfindet. Die Unterprogramme greifen auf die globale interne Tabelle zu. In einem Anwendungsprogramm
wird der Funktionsbaustein zum Abheben für verschiedene Konten aufgerufen. Die Ausnahmebehandlung
erfolgt klassisch mit weiteren CASE-WHEN-Kontrollstrukturen für die Abfrage von sy-subrc.
-
FUNCTION-POOL account.
-
DATA account_tab TYPE SORTED TABLE OF accounts
WITH UNIQUE KEY id.
-
LOAD-OF-PROGRAM.
"fetch amount for all accounts into account_tab
...
...
FUNCTION withdraw.
*"-----------------------------------------------------
*" IMPORTING
*" REFERENCE(id) TYPE accounts-id
*" REFERENCE(kind) TYPE c DEFAULT 'C'
*" REFERENCE(amount) TYPE accounts-amount
*" EXCEPTIONS
*" negative_amount
*" unknown_account_type
*"------------------------------------------------------
CASE kind.
WHEN 'C'.
PERFORM withdraw_from_checking_account
USING id amount.
WHEN 'S'.
PERFORM withdraw_from_savings_account
USING id amount.
WHEN OTHERS.
RAISE unknown_account_type.
ENDCASE.
ENDFUNCTION.
-
FORM withdraw_from_checking_account
USING l_id TYPE accounts-id
l_amount TYPE accounts-amount.
FIELD-SYMBOLS <account> TYPE accounts.
ASSIGN account_tab[ KEY primary_key id = l_id ] TO <account>.
<account> = <account> - l_amount.
IF <account> < 0.
"Handle debit balance
...
ENDIF.
ENDFORM.
-
FORM withdraw_from_savings_account
USING l_id TYPE accounts-id
l_amount TYPE accounts-amount.
FIELD-SYMBOLS <account> TYPE accounts.
ASSIGN account_tab[ KEY primary_key id = l_id ] TO <account>.
IF <account>-amount > l_amount.
<account>-amount = <account>-amount - l_amount.
ELSE.
RAISE negative_amount.
ENDIF.
ENDFORM.
-
*********************************************************
-
PROGRAM bank_application.
...
-
CALL FUNCTION 'WITHDRAW'
EXPORTING
id
= ...
kind = 'C'
amount = ...
EXCEPTIONS
unknown_account_type = 2
negative_amount = 4.
CASE sy-subrc.
WHEN 2.
...
WHEN 4.
...
ENDCASE.
...
CALL FUNCTION 'WITHDRAW'
EXPORTING
id
= ...
kind = 'S'
amount = ...
EXCEPTIONS
unknown_account_type = 2
negative_amount = 4.
CASE sy-subrc.
WHEN 2.
...
WHEN 4.
...
ENDCASE.
Gutes Beispiel
Folgender Quelltext enthält eine ansatzweise Implementierung der Behandlung von verschiedenen
Arten von Bankkonten in Klassen und deren Verwendung in einer Klasse, wobei wieder nur die Funktion "Abheben eines Betrags" gezeigt wird.
Die verschiedenen Kontoarten werden in Unterklassen einer abstrakten Klasse für Konten implementiert.
Jede Instanz eines Kontos wird in ihrem Konstruktor genau mit den Daten versorgt, die sie benötigt.
Die Anwendungsklasse erzeugt je nach Bedarf Instanzen von Konten der gewünschten Art und verwendet
deren Methoden polymorph über eine Oberklassenreferenzvariable. Die Ausnahmebehandlung erfolgt
über klassenbasierte Ausnahmen. Es werden keine CASE-WHEN-Kontrollstrukturen benötigt. Wie bereits bei
Trennung der Belange
angekündigt, entsteht hier bei der Verwendung von Klassen kein Overhead an Code mehr gegenüber der prozeduralen Programmierung.
-
CLASS cx_negative_amount DEFINITION PUBLIC
INHERITING FROM cx_static_check.
ENDCLASS.
-
CLASS cl_account DEFINITION ABSTRACT PUBLIC.
PUBLIC SECTION.
METHODS: constructor IMPORTING id TYPE string,
withdraw IMPORTING amount TYPE i
RAISING cx_negative_amount.
PROTECTED SECTION.
DATA amount TYPE accounts-amount.
ENDCLASS.
-
CLASS cl_account IMPLEMENTATION.
METHOD constructor.
"fetch amount for one account into attribute amount
...
ENDMETHOD.
METHOD withdraw.
me->amount = me->amount - amount.
ENDMETHOD.
ENDCLASS.
-
CLASS cl_checking_account DEFINITION PUBLIC
INHERITING FROM cl_account.
PUBLIC SECTION.
METHODS withdraw REDEFINITION.
ENDCLASS.
-
CLASS cl_checking_account IMPLEMENTATION.
METHOD withdraw.
super->withdraw( amount ).
IF me->amount < 0.
"Handle debit balance
...
ENDIF.
ENDMETHOD.
ENDCLASS.
-
CLASS cl_savings_account DEFINITION PUBLIC
INHERITING FROM cl_account.
PUBLIC SECTION.
METHODS withdraw REDEFINITION.
ENDCLASS.
-
CLASS cl_savings_account IMPLEMENTATION.
METHOD withdraw.
IF me->amount > amount.
super->withdraw( amount ).
ELSE.
RAISE EXCEPTION TYPE cx_negative_amount.
ENDIF.
ENDMETHOD.
ENDCLASS.
********************************************************
-
CLASS bank_application DEFINITION PUBLIC.
PUBLIC SECTION.
CLASS-METHODS main.
ENDCLASS.
-
CLASS bank_application IMPLEMENTATION.
METHOD main.
DATA: account1 TYPE REF TO cl_account,
account2 TYPE REF TO cl_account.
...
-
CREATE OBJECT account1 TYPE cl_checking_account
EXPORTING
id = ...
-
CREATE OBJECT account2 TYPE cl_savings_account
EXPORTING
id = ...
...
-
TRY.
account1->withdraw( ... ).
account2->withdraw( ... ).
CATCH cx_negative_amount.
...
ENDTRY.
ENDMETHOD.
ENDCLASS.