OO-ABAP: classes, interfaces & exceptions
Objectgeoriënteerd ABAP van begin tot eind: classes en visibility, constructors, interfaces, overerving en casting, events, en class-based exception handling met TRY/CATCH.
01Classes: definitie & implementatie
Object-georiënteerd ABAP (ABAP Objects) is sinds jaren de standaard voor nieuwe code. Een class bestaat altijd uit twee delen: de DEFINITION (het "wat": attributen, methodesignaturen) en de IMPLEMENTATION (het "hoe": de methodebodies). Globale classes maak je in SE24 (of Eclipse/ADT), lokale classes (lcl_) definieer je gewoon in je programma.
CLASS lcl_factuur DEFINITION.
PUBLIC SECTION.
METHODS:
constructor IMPORTING iv_vbeln TYPE vbeln_vf,
bereken_totaal RETURNING VALUE(rv_totaal) TYPE netwr,
voeg_regel_toe IMPORTING is_regel TYPE ty_regel
RAISING zcx_factuur.
CLASS-METHODS maak_van_order
IMPORTING iv_vbeln TYPE vbeln_va
RETURNING VALUE(ro_obj) TYPE REF TO lcl_factuur.
PROTECTED SECTION.
DATA mt_regels TYPE STANDARD TABLE OF ty_regel.
PRIVATE SECTION.
DATA mv_vbeln TYPE vbeln_vf.
ENDCLASS.
CLASS lcl_factuur IMPLEMENTATION.
METHOD constructor.
mv_vbeln = iv_vbeln.
ENDMETHOD.
METHOD bereken_totaal.
rv_totaal = REDUCE netwr( INIT t TYPE netwr
FOR ls IN mt_regels
NEXT t = t + ls-bedrag ).
ENDMETHOD.
METHOD maak_van_order.
ro_obj = NEW lcl_factuur( iv_vbeln = CONV #( iv_vbeln ) ).
ENDMETHOD.
METHOD voeg_regel_toe. " ... ENDMETHOD.
ENDCLASS.
Zichtbaarheid
| Sectie | Toegankelijk voor |
|---|---|
PUBLIC SECTION | Iedereen — dit is je API. Houd hem klein. |
PROTECTED SECTION | De class zelf + subclasses. |
PRIVATE SECTION | Alleen de class zelf (en FRIENDS). |
Instance vs. static
METHODS/DATAhoren bij een object: aanroepen metlo_obj->methode( ), attribuutprefixmv_/mt_/mo_.CLASS-METHODS/CLASS-DATAhoren bij de class: aanroepen metzcl_x=>methode( ), prefixgv_/sv_. Statische data wordt gedeeld door alle instanties.- Objecten maak je met
DATA(lo) = NEW zcl_x( ... )(modern) ofCREATE OBJECT lo EXPORTING ...(klassiek). constructordraait één keer per object;class_constructoréén keer per programma, bij het eerste gebruik van de class.me->verwijst naar het eigen object (zoalsthis); meestal weglaatbaar.
Methodeparameters & aanroepen
| Soort | Prefix-conventie | Betekenis |
|---|---|---|
IMPORTING | iv_ / is_ / it_ / io_ | Invoer (alleen-lezen in de methode). |
EXPORTING | ev_ / es_ / et_ / eo_ | Uitvoer. Let op: wordt niet automatisch leeggemaakt — begin met CLEAR ev_x. |
CHANGING | cv_ / cs_ / ct_ | In/uitvoer. |
RETURNING VALUE(rv_) | rv_ / rs_ / rt_ / ro_ | Functioneel resultaat; maakt ketens en expressies mogelijk. Maximaal één, altijd by-value. |
RAISING | — | Declareert welke exception-classes kunnen optreden. |
" Functionele aanroep — kan overal in expressies
lv_totaal = lo_factuur->bereken_totaal( ).
" Meerdere parameters
lo_factuur->voeg_regel_toe( is_regel = ls_regel ).
" EXPORTING/IMPORTING mix (aanroeperperspectief!)
lo_service->lees( EXPORTING iv_id = lv_id
IMPORTING es_data = ls_data ).
" Method chaining
lv_naam = zcl_klant=>lees( lv_kunnr )->get_naam( ).
02Interfaces
Een interface (globaal: ZIF_, lokaal: lif_) definieert een contract — methodesignaturen zonder implementatie. Classes die het interface opnemen, moeten alle methoden implementeren. Dit is hét mechanisme voor loskoppeling en testbaarheid (mocken in unit tests).
INTERFACE lif_printer.
METHODS print IMPORTING iv_tekst TYPE string.
ENDINTERFACE.
CLASS lcl_pdf_printer DEFINITION.
PUBLIC SECTION.
INTERFACES lif_printer.
ALIASES print FOR lif_printer~print. " korte naam
ENDCLASS.
" Programmeren tegen het interface:
DATA lo_printer TYPE REF TO lif_printer.
lo_printer = NEW lcl_pdf_printer( ).
lo_printer->print( 'Hallo' ).
- Interfacemethoden roep je aan met de volledige naam
lif_printer~print, tenzij je eenALIASESdefinieert. - Een class kan meerdere interfaces opnemen; interfaces kunnen zelf ook interfaces bevatten (compositie).
- Bekende standaardinterfaces:
IF_SERIALIZABLE_OBJECT,IF_OO_ADT_CLASSRUN(console-apps in Eclipse:out->write( )).
03Overerving & polymorfisme
Met INHERITING FROM erft een subclass alle niet-private componenten van de superclass, en kan hij methoden herdefiniëren.
CLASS lcl_express_order DEFINITION INHERITING FROM lcl_order.
PUBLIC SECTION.
METHODS bereken_kosten REDEFINITION.
ENDCLASS.
CLASS lcl_express_order IMPLEMENTATION.
METHOD bereken_kosten.
rv_kosten = super->bereken_kosten( ) + 10. " superclass-versie + toeslag
ENDMETHOD.
ENDCLASS.
- ABAP kent enkelvoudige overerving: één superclass; meerdere contracten regel je met interfaces.
ABSTRACT-classes kunnen niet geïnstantieerd worden;ABSTRACT-methoden hebben geen implementatie en moeten geredefinieerd worden.FINAL-classes kunnen geen subclasses krijgen;FINAL-methoden niet geredefinieerd.- De constructor van de subclass moet éérst
super->constructor( ... )aanroepen voordat hij eigen attributen aanraakt. CREATE PRIVATE/PROTECTEDin de definitie beperkt wieNEWmag doen — de basis van factory- en singleton-patronen.
04Casting: up- & downcast
Een referentie naar een subclass past altijd in een referentie naar de superclass of een interface (upcast, impliciet). De omgekeerde weg (downcast) moet expliciet en kan mislukken.
DATA lo_order TYPE REF TO lcl_order.
lo_order = NEW lcl_express_order( ). " upcast: automatisch
" Downcast: expliciet met CAST (of oud: ?=)
TRY.
DATA(lo_express) = CAST lcl_express_order( lo_order ).
CATCH cx_sy_move_cast_error.
" lo_order was géén express-order
ENDTRY.
" Veilig testen vóór de cast
IF lo_order IS INSTANCE OF lcl_express_order.
lo_express = CAST #( lo_order ).
ENDIF.
" Of per type verwerken
CASE TYPE OF lo_order.
WHEN TYPE lcl_express_order INTO DATA(lo_exp). " ...
WHEN OTHERS. " ...
ENDCASE.
05Events
Classes kunnen gebeurtenissen publiceren waar andere objecten zich op abonneren — veel gebruikt in GUI-programmering (ALV-dubbelklik bijvoorbeeld).
" In de publicerende class:
EVENTS voltooid EXPORTING VALUE(ev_id) TYPE i.
...
RAISE EVENT voltooid EXPORTING ev_id = 42.
" In de luisterende class:
METHODS on_voltooid FOR EVENT voltooid OF lcl_taak
IMPORTING ev_id.
" Koppelen:
SET HANDLER lo_listener->on_voltooid FOR lo_taak.
SET HANDLER lo_listener->on_voltooid FOR ALL INSTANCES.
06Class-based exceptions: TRY/CATCH
Moderne foutafhandeling gebruikt exception-classes (CX_, eigen: ZCX_) in plaats van sy-subrc-codes.
TRY.
DATA(lo_factuur) = zcl_factuur=>lees( iv_vbeln = lv_vbeln ).
lo_factuur->boek( ).
CATCH zcx_niet_gevonden INTO DATA(lx_nf).
MESSAGE lx_nf->get_text( ) TYPE 'S' DISPLAY LIKE 'E'.
CATCH zcx_factuur cx_sy_arithmetic_error INTO DATA(lx_alg).
" meerdere types in één CATCH
CLEANUP.
" opruimen als de exception hogerop wordt afgehandeld
ENDTRY.
" Zelf gooien:
RAISE EXCEPTION NEW zcx_niet_gevonden( vbeln = lv_vbeln ).
" of klassiek: RAISE EXCEPTION TYPE zcx_niet_gevonden EXPORTING vbeln = ...
De drie basisklassen
| Superclass | Declaratieplicht (RAISING) | Gebruik voor |
|---|---|---|
CX_STATIC_CHECK | Ja — compiler dwingt declaratie of afvangen af | Verwachtbare businessfouten ("order niet gevonden"). Default. |
CX_DYNAMIC_CHECK | Nee (check pas at runtime) | Fouten die de aanroeper meestal kan uitsluiten (delen door nul). |
CX_NO_CHECK | Nee, mag ook niet | Onherstelbare systeemfouten; propageren altijd. |
- Eigen exception-classes erven van (een subclass van) een van deze drie en definiëren teksten via het
IF_T100_MESSAGE-interface, gekoppeld aan berichtklassen. lx->get_text( )geeft de fouttekst;lx->previousbevat een eventuele geneste oorzaak (exception chaining).- Een niet-afgevangen exception leidt tot een ST22-dump — vang dus af op het niveau waar je er iets zinnigs mee kunt.
- Functiemodules gebruiken vaak nog klassieke exceptions (sy-subrc); beide werelden bestaan naast elkaar.
07ABAP Unit: testen
Unit tests schrijf je als lokale testclasses (FOR TESTING) bij je programma of globale class; uitvoeren met Ctrl+Shift+F10 (SE80/ADT).
CLASS ltc_factuur DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS totaal_van_lege_factuur_is_nul FOR TESTING.
METHODS setup. " draait vóór elke test
DATA mo_cut TYPE REF TO lcl_factuur. " cut = code under test
ENDCLASS.
CLASS ltc_factuur IMPLEMENTATION.
METHOD setup.
mo_cut = NEW lcl_factuur( '0000000001' ).
ENDMETHOD.
METHOD totaal_van_lege_factuur_is_nul.
cl_abap_unit_assert=>assert_equals(
act = mo_cut->bereken_totaal( )
exp = 0
msg = 'Lege factuur moet totaal 0 hebben' ).
ENDMETHOD.
ENDCLASS.
Veelgebruikte asserts: assert_equals, assert_true, assert_initial, assert_bound, fail. Afhankelijkheden (database, tijd) isoleer je via interfaces en test doubles; voor database-toegang bestaat bovendien CL_OSQL_TEST_ENVIRONMENT.