Heritage ABAP : INHERITING FROM, ABSTRACT et FINAL

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

L’heritage est un concept central de la POO qui permet la reutilisation et l’extension de classes. En ABAP, l’heritage est realise avec INHERITING FROM. ABSTRACT et FINAL controlent l’extensibilite.

Concepts de base

  • Heritage : Une classe (sous-classe) herite des proprietes d’une autre (classe de base)
  • ABSTRACT : Classes/methodes qui ne peuvent pas etre instanciees/appelees directement
  • FINAL : Classes/methodes qui ne peuvent pas etre heritees/redefinies
  • REDEFINITION : Redefinir des methodes de la classe de base dans la sous-classe

Syntaxe

Heritage

CLASS <sous_classe> DEFINITION
INHERITING FROM <classe_base>.

Abstract

CLASS <nom_classe> DEFINITION ABSTRACT.
METHODS: <nom_methode> ABSTRACT.
ENDCLASS.

Final

CLASS <nom_classe> DEFINITION FINAL.
METHODS: <nom_methode> FINAL.
ENDCLASS.

Redefinition

CLASS <sous_classe> DEFINITION INHERITING FROM <classe_base>.
METHODS: <nom_methode> REDEFINITION.
ENDCLASS.

Exemples

1. Heritage simple

" Classe de base
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: / 'Vehicule demarre...'.
ENDMETHOD.
ENDCLASS.
" Sous-classe
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.
" Appeler le constructeur de la classe de base
super->constructor( iv_brand ).
mv_doors = iv_doors.
ENDMETHOD.
METHOD get_doors.
rv_doors = mv_doors.
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA: lo_car TYPE REF TO lcl_car.
lo_car = NEW #( iv_brand = 'BMW' iv_doors = 4 ).
WRITE: / lo_car->get_brand( ). " BMW (herite)
WRITE: / lo_car->get_doors( ). " 4 (methode propre)
lo_car->start( ). " Vehicule demarre... (herite)

2. Redefinition de methodes (REDEFINITION)

CLASS lcl_car DEFINITION INHERITING FROM lcl_vehicle.
PUBLIC SECTION.
METHODS: constructor IMPORTING iv_brand TYPE string
iv_doors TYPE i,
start REDEFINITION. " Redefinir la methode
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.
" Implementation propre
WRITE: / 'La voiture demarre le moteur...'.
" Optionnel : Appeler la methode de la classe de base
" super->start( ).
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA: lo_vehicle TYPE REF TO lcl_vehicle,
lo_car TYPE REF TO lcl_car.
lo_vehicle = NEW lcl_vehicle( 'Generic' ).
lo_vehicle->start( ). " Vehicule demarre...
lo_car = NEW lcl_car( iv_brand = 'BMW' iv_doors = 4 ).
lo_car->start( ). " La voiture demarre le moteur...

3. SUPER - Appeler la classe de base

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.
" Appeler le constructeur de la classe parente
super->constructor(
iv_brand = iv_brand
iv_doors = iv_doors
).
mv_battery_capacity = iv_battery.
ENDMETHOD.
METHOD start.
WRITE: / 'Verification de la batterie...'.
WRITE: / |Capacite : { mv_battery_capacity } kWh|.
" Appeler la methode de la classe de base
super->start( ).
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA(lo_tesla) = NEW lcl_electric_car(
iv_brand = 'Tesla"
iv_doors = 4
iv_battery = 100
).
lo_tesla->start( ).
" Sortie :
" Verification de la batterie...
" Capacite : 100 kWh
" La voiture demarre le moteur...

4. Classes abstraites

" Classe abstraite - ne peut pas etre instanciee
CLASS lcl_shape DEFINITION ABSTRACT.
PUBLIC SECTION.
" Methode abstraite - DOIT etre implementee dans la sous-classe
METHODS: calculate_area ABSTRACT
RETURNING VALUE(rv_area) TYPE f.
" Methode concrete - peut etre heritee
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 n'a pas d'implementation (abstraite)
ENDCLASS.
" Sous-classe concrete : Cercle
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 = 'Cercle'.
mv_radius = iv_radius.
ENDMETHOD.
METHOD calculate_area.
rv_area = c_pi * mv_radius ** 2.
ENDMETHOD.
ENDCLASS.
" Sous-classe concrete : Rectangle
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 = 'Rectangle'.
mv_width = iv_width.
mv_height = iv_height.
ENDMETHOD.
METHOD calculate_area.
rv_area = mv_width * mv_height.
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA: lt_shapes TYPE TABLE OF REF TO lcl_shape.
" lo_shape = NEW lcl_shape( ). " ERREUR ! Abstraite !
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( ), ': Surface =', lo_shape->calculate_area( ).
ENDLOOP.
" Sortie :
" Cercle : Surface = 78.54
" Rectangle : Surface = 12.00

5. FINAL - Empecher l’heritage

" Classe finale - ne peut PAS etre heritee
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.
" Constructeur prive
ENDMETHOD.
METHOD get_instance.
IF go_instance IS NOT BOUND.
go_instance = NEW #( ).
ENDIF.
ro_instance = go_instance.
ENDMETHOD.
ENDCLASS.
" Cette ligne provoquerait une erreur de syntaxe :
" CLASS lcl_child DEFINITION INHERITING FROM lcl_singleton.
" ...
" ENDCLASS.

6. Methodes FINAL

CLASS lcl_base DEFINITION.
PUBLIC SECTION.
" Cette methode ne peut PAS etre redefinie
METHODS: critical_operation FINAL.
" Cette methode peut etre redefinie
METHODS: normal_operation.
ENDCLASS.
CLASS lcl_base IMPLEMENTATION.
METHOD critical_operation.
WRITE: / 'Operation critique - non modifiable'.
ENDMETHOD.
METHOD normal_operation.
WRITE: / 'Operation normale'.
ENDMETHOD.
ENDCLASS.
CLASS lcl_derived DEFINITION INHERITING FROM lcl_base.
PUBLIC SECTION.
" OK : normal_operation peut etre redefinie
METHODS: normal_operation REDEFINITION.
" ERREUR : critical_operation est FINAL
" METHODS: critical_operation REDEFINITION.
ENDCLASS.
CLASS lcl_derived IMPLEMENTATION.
METHOD normal_operation.
WRITE: / 'Operation redefinie'.
ENDMETHOD.
ENDCLASS.

7. PROTECTED - Acces pour les sous-classes

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.
" Visible uniquement pour cette classe et les sous-classes
DATA: mv_balance TYPE p DECIMALS 2.
METHODS: validate_amount IMPORTING iv_amount TYPE p
RETURNING VALUE(rv_valid) TYPE abap_bool.
PRIVATE SECTION.
" Visible uniquement pour cette classe
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.
" Acces au mv_balance PROTECTED possible
DATA(lv_interest) = mv_balance * iv_rate / 100.
" Acces a validate_amount PROTECTED possible
IF validate_amount( lv_interest ).
mv_balance = mv_balance + lv_interest.
ENDIF.
" ERREUR : mv_account_number est PRIVATE
" mv_account_number = '123'.
ENDMETHOD.
ENDCLASS.

8. Polymorphisme

" Differents types de vehicules
CLASS lcl_motorcycle DEFINITION INHERITING FROM lcl_vehicle.
PUBLIC SECTION.
METHODS: start REDEFINITION.
ENDCLASS.
CLASS lcl_motorcycle IMPLEMENTATION.
METHOD start.
WRITE: / 'La moto vrombrit...'.
ENDMETHOD.
ENDCLASS.
CLASS lcl_truck DEFINITION INHERITING FROM lcl_vehicle.
PUBLIC SECTION.
METHODS: start REDEFINITION.
ENDCLASS.
CLASS lcl_truck IMPLEMENTATION.
METHOD start.
WRITE: / 'Le camion diesel demarre...'.
ENDMETHOD.
ENDCLASS.
" Utilisation polymorphe
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.
" Tous les vehicules demarrent - chacun a sa maniere
LOOP AT lt_fleet INTO DATA(lo_vehicle).
lo_vehicle->start( ).
ENDLOOP.
" Sortie :
" La voiture demarre le moteur...
" La moto vrombrit...
" Le camion diesel demarre...

9. Verification de type avec IS INSTANCE OF

DATA: lo_vehicle TYPE REF TO lcl_vehicle.
lo_vehicle = NEW lcl_car( iv_brand = 'Audi' iv_doors = 4 ).
" Verification de type
IF lo_vehicle IS INSTANCE OF lcl_car.
WRITE: / 'C est une voiture'.
" Downcast pour methodes specifiques
DATA(lo_car) = CAST lcl_car( lo_vehicle ).
WRITE: / 'Portes :', lo_car->get_doors( ).
ENDIF.
" Avec CASE TYPE OF
CASE TYPE OF lo_vehicle.
WHEN TYPE lcl_car.
WRITE: / 'Voiture detectee'.
WHEN TYPE lcl_motorcycle.
WRITE: / 'Moto detectee'.
WHEN TYPE lcl_truck.
WRITE: / 'Camion detecte'.
WHEN OTHERS.
WRITE: / 'Type de vehicule inconnu'.
ENDCASE.

10. Classe abstraite avec Template Method Pattern

CLASS lcl_report DEFINITION ABSTRACT.
PUBLIC SECTION.
" Template Method - definit le deroulement
METHODS: execute FINAL.
PROTECTED SECTION.
" Hook Methods - a implementer par les sous-classes
METHODS: fetch_data ABSTRACT,
process_data ABSTRACT,
display_output ABSTRACT.
ENDCLASS.
CLASS lcl_report IMPLEMENTATION.
METHOD execute.
" Deroulement fixe, non redefinissable (FINAL)
WRITE: / 'Rapport demarre...'.
fetch_data( ).
process_data( ).
display_output( ).
WRITE: / 'Rapport termine.'.
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: / ' Chargement des donnees de vente...'.
ENDMETHOD.
METHOD process_data.
WRITE: / ' Calcul du chiffre d affaires...'.
ENDMETHOD.
METHOD display_output.
WRITE: / ' Affichage du rapport de vente.'.
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA(lo_report) = NEW lcl_sales_report( ).
lo_report->execute( ).
" Sortie :
" Rapport demarre...
" Chargement des donnees de vente...
" Calcul du chiffre d affaires...
" Affichage du rapport de vente.
" Rapport termine.

Hierarchie d’heritage

lcl_vehicle (classe de base)
├── lcl_car
│ │
│ └── lcl_electric_car
├── lcl_motorcycle
└── lcl_truck

Resume

ConceptMot-cleDescription
HeritageINHERITING FROMUne classe etend une autre classe
RedefinitionREDEFINITIONReimplementer une methode de la classe de base
Appel classe de basesuper->Acces a la classe parente
AbstraitABSTRACTNon instanciable / doit etre implemente
FinalFINALNon heritable / non redefinissable
ProtegePROTECTEDVisible pour la classe et les sous-classes

Remarques importantes / Bonnes pratiques

  • Heritage pour les relations “est-un” (Voiture est un Vehicule).
  • Pour les relations “a-un”, preferer la composition.
  • PROTECTED pour les attributs/methodes dont les sous-classes ont besoin.
  • ABSTRACT pour les classes de base qui servent uniquement de modele.
  • FINAL pour les classes/methodes qui ne doivent pas etre modifiees.
  • Le polymorphisme permet un traitement uniforme de differents types.
  • super-> pour appeler l’implementation de la classe de base.
  • Verifiez les types avec IS INSTANCE OF avant le CAST.
  • Preferez les INTERFACE pour un couplage faible.
  • Evitez les hierarchies d’heritage profondes (max. 2-3 niveaux).