ABAP INTERFACE : Interfaces pour la programmation polymorphe

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

Une INTERFACE en ABAP definit un contrat (Contract) que les classes doivent remplir. Elle specifie quelles methodes une classe doit fournir, sans prescrire leur implementation.

Concept de base

  • Une interface definit uniquement les signatures des methodes (et optionnellement des attributs/constantes).
  • Une classe implemente l’interface et fournit la logique concrete.
  • Polymorphisme : Differentes classes peuvent implementer la meme interface mais reagir differemment.

Syntaxe

Definir une interface

INTERFACE <nom_interface>.
METHODS: <methode1> [IMPORTING/EXPORTING/RETURNING ...],
<methode2> [...].
DATA: <attribut> TYPE <type>.
CONSTANTS: <constante> TYPE <type> VALUE <valeur>.
ENDINTERFACE.

Implementer une interface dans une classe

CLASS <nom_classe> DEFINITION.
PUBLIC SECTION.
INTERFACES: <nom_interface>.
" Optionnel : Alias pour les methodes d'interface
ALIASES: <alias> FOR <nom_interface>~<methode>.
ENDCLASS.
CLASS <nom_classe> IMPLEMENTATION.
METHOD <nom_interface>~<methode>.
" Implementation
ENDMETHOD.
ENDCLASS.

Exemples

1. Interface simple

" Definir l'interface
INTERFACE lif_printable.
METHODS: print RETURNING VALUE(rv_output) TYPE string.
ENDINTERFACE.
" Classe 1 : Document
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 = |Document : { mv_title }|.
ENDMETHOD.
ENDCLASS.
" Classe 2 : Facture
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 = |Facture { mv_number }: { mv_amount } EUR|.
ENDMETHOD.
ENDCLASS.
" Utilisation avec polymorphisme
DATA: lt_printables TYPE TABLE OF REF TO lif_printable,
lo_printable TYPE REF TO lif_printable.
" Differents objets dans une liste
APPEND NEW lcl_document( 'Contrat' ) TO lt_printables.
APPEND NEW lcl_invoice( iv_number = 'F-001' iv_amount = '1500.00' ) TO lt_printables.
" Traiter tous de la meme maniere
LOOP AT lt_printables INTO lo_printable.
WRITE: / lo_printable->print( ).
ENDLOOP.
" Sortie :
" Document : Contrat
" Facture F-001: 1500.00 EUR

2. Interface avec alias

Avec ALIASES, vous pouvez attribuer des noms plus courts aux methodes d’interface :

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 pour un acces plus simple
ALIASES: add FOR lif_calculator~calculate.
ENDCLASS.
CLASS lcl_adder IMPLEMENTATION.
METHOD lif_calculator~calculate.
rv_result = iv_a + iv_b.
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA: lo_adder TYPE REF TO lcl_adder.
lo_adder = NEW #( ).
" Avec nom complet
DATA(lv_result1) = lo_adder->lif_calculator~calculate( iv_a = 5 iv_b = 3 ).
" Avec alias (plus court)
DATA(lv_result2) = lo_adder->add( iv_a = 5 iv_b = 3 ).

3. Implementer plusieurs interfaces

Une classe peut implementer plusieurs interfaces :

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 avec constantes et attributs

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.
" Utilisation des constantes d'interface
DATA: lo_order TYPE REF TO lcl_order.
lo_order = NEW #( ).
lo_order->lif_status~set_status( lif_status=>c_active ).

5. Heritage d’interface (Interface composee)

Les interfaces peuvent inclure d’autres interfaces :

INTERFACE lif_readable.
METHODS: read RETURNING VALUE(rv_data) TYPE string.
ENDINTERFACE.
INTERFACE lif_writable.
METHODS: write IMPORTING iv_data TYPE string.
ENDINTERFACE.
" Interface composee
INTERFACE lif_read_write.
INTERFACES: lif_readable,
lif_writable.
METHODS: clear.
ENDINTERFACE.
" Une classe qui implemente lif_read_write
" doit implementer toutes les methodes des trois interfaces
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. Injection de dependances avec interfaces

Les interfaces permettent un couplage faible et une meilleure testabilite :

" Interface pour l'acces a la base de donnees
INTERFACE lif_customer_repository.
METHODS: get_customer IMPORTING iv_id TYPE i
RETURNING VALUE(rs_customer) TYPE zcustomer
RAISING cx_not_found.
ENDINTERFACE.
" Implementation productive
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 pour les tests unitaires
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 = 'Client Test' ).
ENDMETHOD.
ENDCLASS.
" Le service utilise l'interface (pas la classe concrete)
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 = 'Inconnu'.
ENDTRY.
ENDMETHOD.
ENDCLASS.

Interface vs. Classe abstraite

AspectInterfaceClasse abstraite
Heritage multipleOui (plusieurs interfaces)Non (une seule classe parente)
ImplementationAucunePeut implementer partiellement
AttributsOui (mais inhabituel)Oui
ConstructeurNonOui
UtilisationContrats, polymorphismeLogique de base commune

Verification de type et casting

DATA: lo_object TYPE REF TO object,
lo_printable TYPE REF TO lif_printable.
lo_object = NEW lcl_document( 'Test' ).
" Verifier si l'interface est implementee
IF lo_object IS INSTANCE OF lif_printable.
" Downcast vers l'interface
lo_printable ?= lo_object.
WRITE: / lo_printable->print( ).
ENDIF.

Remarques importantes / Bonnes pratiques

  • Les interfaces commencent souvent par lif_ (locale) ou zif_ / if_ (globale).
  • Utilisez les interfaces pour un couplage faible et une meilleure testabilite.
  • Preferez les interfaces a l’heritage pour le polymorphisme.
  • Gardez les interfaces petites et ciblees (Interface Segregation Principle).
  • Utilisez ALIASES pour les methodes d’interface frequemment utilisees.
  • Les constantes d’interface sont utiles pour les codes de statut et les enumerations.
  • Combinez les interfaces avec TRY...CATCH pour une gestion robuste des erreurs.
  • Utilisez CLASS pour l’implementation des interfaces.