Les Design Patterns sont des modeles de solution eprouves pour des problemes recurrents dans le developpement logiciel. Dans le contexte de RAP (RESTful ABAP Programming), ils aident a developper des Business Objects flexibles, maintenables et extensibles.
Pourquoi les Design Patterns dans RAP ?
| Defi | Design Pattern | Avantage |
|---|---|---|
| Creation d’objets differente | Factory | Decouplage de l’implementation |
| Algorithmes changeants | Strategy | Logique metier interchangeable |
| Structure de processus commune | Template Method | Cadre reutilisable |
| Sous-systemes complexes | Facade | Interface simplifiee |
Pattern Factory
Le Pattern Factory encapsule la creation d’objets et permet de determiner l’implementation concrete a l’execution. Dans RAP, cela est particulierement utile pour :
- Differentes logiques de calcul selon le contexte metier
- Implementations specifiques au mandant ou au pays
- Testabilite par remplacement d’implementations de production par des mocks
Scenario : Calcul de prix par region
" Interface pour le calcul de prixINTERFACE zif_price_calculator. METHODS calculate_price IMPORTING iv_base_price TYPE decfloat34 iv_quantity TYPE i RETURNING VALUE(rv_result) TYPE decfloat34.ENDINTERFACE.
" Implementation pour la FranceCLASS zcl_price_calc_fr DEFINITION. PUBLIC SECTION. INTERFACES zif_price_calculator.ENDCLASS.
CLASS zcl_price_calc_fr IMPLEMENTATION. METHOD zif_price_calculator~calculate_price. " TVA francaise : 20% rv_result = iv_base_price * iv_quantity * '1.20'. ENDMETHOD.ENDCLASS.
" Implementation pour la SuisseCLASS zcl_price_calc_ch DEFINITION. PUBLIC SECTION. INTERFACES zif_price_calculator.ENDCLASS.
CLASS zcl_price_calc_ch IMPLEMENTATION. METHOD zif_price_calculator~calculate_price. " TVA suisse : 8.1% rv_result = iv_base_price * iv_quantity * '1.081'. ENDMETHOD.ENDCLASS.Classe Factory
CLASS zcl_price_calculator_factory DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION. CLASS-METHODS get_instance IMPORTING iv_country_code TYPE land1 RETURNING VALUE(ro_calculator) TYPE REF TO zif_price_calculator.
PRIVATE SECTION. CLASS-DATA gt_instances TYPE HASHED TABLE OF REF TO zif_price_calculator WITH UNIQUE KEY table_line.ENDCLASS.
CLASS zcl_price_calculator_factory IMPLEMENTATION. METHOD get_instance. CASE iv_country_code. WHEN 'FR'. ro_calculator = NEW zcl_price_calc_fr( ). WHEN 'CH'. ro_calculator = NEW zcl_price_calc_ch( ). WHEN 'BE'. ro_calculator = NEW zcl_price_calc_be( ). WHEN OTHERS. ro_calculator = NEW zcl_price_calc_default( ). ENDCASE. ENDMETHOD.ENDCLASS.Utilisation dans l’implementation du Behavior RAP
CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS calculate_total FOR DETERMINE ON SAVE IMPORTING keys FOR Order~calculateTotal.ENDCLASS.
CLASS lhc_order IMPLEMENTATION. METHOD calculate_total. READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order FIELDS ( CountryCode BasePrice Quantity ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order). " La Factory fournit la bonne implementation DATA(lo_calculator) = zcl_price_calculator_factory=>get_instance( iv_country_code = ls_order-CountryCode ).
DATA(lv_total) = lo_calculator->calculate_price( iv_base_price = ls_order-BasePrice iv_quantity = ls_order-Quantity ).
MODIFY ENTITIES OF zi_order IN LOCAL MODE ENTITY Order UPDATE FIELDS ( TotalPrice ) WITH VALUE #( ( %tky = ls_order-%tky TotalPrice = lv_total ) ). ENDLOOP. ENDMETHOD.ENDCLASS.Pattern Strategy
Le Pattern Strategy permet d’echanger des algorithmes a l’execution. Contrairement au Pattern Factory, qui encapsule la creation, Strategy encapsule le comportement.
Scenario : Differentes strategies de remise
" Interface StrategyINTERFACE zif_discount_strategy. METHODS apply_discount IMPORTING iv_price TYPE decfloat34 is_customer TYPE zcustomer RETURNING VALUE(rv_result) TYPE decfloat34.ENDINTERFACE.
" Strategie sans remiseCLASS zcl_no_discount DEFINITION. PUBLIC SECTION. INTERFACES zif_discount_strategy.ENDCLASS.
CLASS zcl_no_discount IMPLEMENTATION. METHOD zif_discount_strategy~apply_discount. rv_result = iv_price. ENDMETHOD.ENDCLASS.
" Strategie de remise en pourcentageCLASS zcl_percentage_discount DEFINITION. PUBLIC SECTION. INTERFACES zif_discount_strategy. METHODS constructor IMPORTING iv_percentage TYPE decfloat34. PRIVATE SECTION. DATA mv_percentage TYPE decfloat34.ENDCLASS.
CLASS zcl_percentage_discount IMPLEMENTATION. METHOD constructor. mv_percentage = iv_percentage. ENDMETHOD.
METHOD zif_discount_strategy~apply_discount. rv_result = iv_price * ( 1 - mv_percentage / 100 ). ENDMETHOD.ENDCLASS.
" Remise client VIP avec echelonnementCLASS zcl_vip_discount DEFINITION. PUBLIC SECTION. INTERFACES zif_discount_strategy.ENDCLASS.
CLASS zcl_vip_discount IMPLEMENTATION. METHOD zif_discount_strategy~apply_discount. " Remise echelonnee basee sur le CA client CASE is_customer-customer_tier. WHEN 'GOLD'. rv_result = iv_price * '0.85'. " 15% de remise WHEN 'SILVER'. rv_result = iv_price * '0.90'. " 10% de remise WHEN 'BRONZE'. rv_result = iv_price * '0.95'. " 5% de remise WHEN OTHERS. rv_result = iv_price. ENDCASE. ENDMETHOD.ENDCLASS.Classe Context avec Strategy
CLASS zcl_order_processor DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS constructor IMPORTING io_discount_strategy TYPE REF TO zif_discount_strategy.
METHODS set_discount_strategy IMPORTING io_discount_strategy TYPE REF TO zif_discount_strategy.
METHODS calculate_final_price IMPORTING iv_price TYPE decfloat34 is_customer TYPE zcustomer RETURNING VALUE(rv_result) TYPE decfloat34.
PRIVATE SECTION. DATA mo_discount_strategy TYPE REF TO zif_discount_strategy.ENDCLASS.
CLASS zcl_order_processor IMPLEMENTATION. METHOD constructor. mo_discount_strategy = io_discount_strategy. ENDMETHOD.
METHOD set_discount_strategy. mo_discount_strategy = io_discount_strategy. ENDMETHOD.
METHOD calculate_final_price. rv_result = mo_discount_strategy->apply_discount( iv_price = iv_price is_customer = is_customer ). ENDMETHOD.ENDCLASS.Utilisation dans une RAP Action
CLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS apply_campaign_discount FOR MODIFY IMPORTING keys FOR ACTION SalesOrder~applyCampaignDiscount RESULT result.ENDCLASS.
CLASS lhc_salesorder IMPLEMENTATION. METHOD apply_campaign_discount. READ ENTITIES OF zi_salesorder IN LOCAL MODE ENTITY SalesOrder ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order). " Choisir la Strategy basee sur la campagne active DATA lo_strategy TYPE REF TO zif_discount_strategy.
IF ls_order-CampaignCode = 'SUMMER25'. lo_strategy = NEW zcl_percentage_discount( 25 ). ELSEIF ls_order-CustomerTier IS NOT INITIAL. lo_strategy = NEW zcl_vip_discount( ). ELSE. lo_strategy = NEW zcl_no_discount( ). ENDIF.
DATA(lo_processor) = NEW zcl_order_processor( lo_strategy ). DATA(lv_final_price) = lo_processor->calculate_final_price( iv_price = ls_order-GrossPrice is_customer = CORRESPONDING #( ls_order ) ).
MODIFY ENTITIES OF zi_salesorder IN LOCAL MODE ENTITY SalesOrder UPDATE FIELDS ( FinalPrice ) WITH VALUE #( ( %tky = ls_order-%tky FinalPrice = lv_final_price ) ). ENDLOOP.
result = VALUE #( FOR order IN lt_orders ( %tky = order-%tky %param = CORRESPONDING #( order ) ) ). ENDMETHOD.ENDCLASS.Pattern Template Method
Le Pattern Template Method definit la structure de base d’un algorithme dans une classe de base, tandis que les sous-classes redefinissent certaines etapes. Le flux reste le meme, les details varient.
Scenario : Generation de documents avec differents formats
" Classe de base abstraite avec Template MethodCLASS zcl_document_generator DEFINITION PUBLIC ABSTRACT CREATE PUBLIC.
PUBLIC SECTION. " Template Method - definit le flux METHODS generate_document FINAL IMPORTING is_order TYPE zorder RETURNING VALUE(rv_document) TYPE string.
PROTECTED SECTION. " Methodes hook - implementees par les sous-classes METHODS get_header ABSTRACT IMPORTING is_order TYPE zorder RETURNING VALUE(rv_header) TYPE string.
METHODS get_line_items ABSTRACT IMPORTING is_order TYPE zorder RETURNING VALUE(rv_items) TYPE string.
METHODS get_footer ABSTRACT IMPORTING is_order TYPE zorder RETURNING VALUE(rv_footer) TYPE string.
METHODS format_output ABSTRACT IMPORTING iv_content TYPE string RETURNING VALUE(rv_formatted) TYPE string.ENDCLASS.
CLASS zcl_document_generator IMPLEMENTATION. METHOD generate_document. " La structure de l'algorithme est fixe DATA(lv_header) = get_header( is_order ). DATA(lv_items) = get_line_items( is_order ). DATA(lv_footer) = get_footer( is_order ).
DATA(lv_content) = |{ lv_header }{ lv_items }{ lv_footer }|.
rv_document = format_output( lv_content ). ENDMETHOD.ENDCLASS.Implementations concretes
" Generateur de document HTMLCLASS zcl_html_document_generator DEFINITION INHERITING FROM zcl_document_generator PUBLIC FINAL CREATE PUBLIC.
PROTECTED SECTION. METHODS get_header REDEFINITION. METHODS get_line_items REDEFINITION. METHODS get_footer REDEFINITION. METHODS format_output REDEFINITION.ENDCLASS.
CLASS zcl_html_document_generator IMPLEMENTATION. METHOD get_header. rv_header = |<html><head><title>Commande { is_order-order_id }</title></head>| && |<body><h1>Commande { is_order-order_id }</h1>| && |<p>Client : { is_order-customer_name }</p>|. ENDMETHOD.
METHOD get_line_items. rv_items = |<table><tr><th>Position</th><th>Article</th><th>Quantite</th></tr>|. LOOP AT is_order-items INTO DATA(ls_item). rv_items = |{ rv_items }<tr><td>{ ls_item-pos }</td>| && |<td>{ ls_item-description }</td>| && |<td>{ ls_item-quantity }</td></tr>|. ENDLOOP. rv_items = |{ rv_items }</table>|. ENDMETHOD.
METHOD get_footer. rv_footer = |<p>Montant total : { is_order-total_amount } EUR</p></body></html>|. ENDMETHOD.
METHOD format_output. rv_formatted = iv_content. ENDMETHOD.ENDCLASS.
" Generateur texte brutCLASS zcl_text_document_generator DEFINITION INHERITING FROM zcl_document_generator PUBLIC FINAL CREATE PUBLIC.
PROTECTED SECTION. METHODS get_header REDEFINITION. METHODS get_line_items REDEFINITION. METHODS get_footer REDEFINITION. METHODS format_output REDEFINITION.ENDCLASS.
CLASS zcl_text_document_generator IMPLEMENTATION. METHOD get_header. rv_header = |========================================\n| && |COMMANDE { is_order-order_id }\n| && |Client : { is_order-customer_name }\n| && |========================================\n|. ENDMETHOD.
METHOD get_line_items. rv_items = |Positions :\n|. LOOP AT is_order-items INTO DATA(ls_item). rv_items = |{ rv_items } { ls_item-pos }. { ls_item-description } - Quantite : { ls_item-quantity }\n|. ENDLOOP. ENDMETHOD.
METHOD get_footer. rv_footer = |----------------------------------------\n| && |TOTAL : { is_order-total_amount } EUR\n| && |========================================|. ENDMETHOD.
METHOD format_output. rv_formatted = iv_content. ENDMETHOD.ENDCLASS.Utilisation dans RAP pour les Actions d’export
CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS export_document FOR MODIFY IMPORTING keys FOR ACTION Order~exportDocument RESULT result.ENDCLASS.
CLASS lhc_order IMPLEMENTATION. METHOD export_document. READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order). DATA lo_generator TYPE REF TO zcl_document_generator.
" Choisir le generateur base sur le parametre CASE ls_order-%param-format. WHEN 'HTML'. lo_generator = NEW zcl_html_document_generator( ). WHEN 'TEXT'. lo_generator = NEW zcl_text_document_generator( ). WHEN 'PDF'. lo_generator = NEW zcl_pdf_document_generator( ). ENDCASE.
DATA(ls_order_data) = CORRESPONDING zorder( ls_order ). DATA(lv_document) = lo_generator->generate_document( ls_order_data ).
" Definir le resultat APPEND VALUE #( %tky = ls_order-%tky %param = VALUE #( content = lv_document format = ls_order-%param-format ) ) TO result. ENDLOOP. ENDMETHOD.ENDCLASS.Pattern Facade
Le Pattern Facade offre une interface simplifiee vers un sous-systeme complexe. Il reduit les dependances et rend l’utilisation de fonctionnalites complexes plus simple.
Scenario : Order Processing Facade
CLASS zcl_order_facade DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS constructor.
" Interface simplifiee - un appel pour un processus complexe METHODS process_complete_order IMPORTING is_order TYPE zorder_create RETURNING VALUE(rs_result) TYPE zorder_result RAISING cx_order_processing.
METHODS cancel_order IMPORTING iv_order_id TYPE zorder_id RETURNING VALUE(rv_success) TYPE abap_bool RAISING cx_order_processing.
PRIVATE SECTION. DATA mo_validator TYPE REF TO zcl_order_validator. DATA mo_inventory TYPE REF TO zcl_inventory_service. DATA mo_payment TYPE REF TO zcl_payment_service. DATA mo_notification TYPE REF TO zcl_notification_service. DATA mo_logger TYPE REF TO zcl_order_logger.ENDCLASS.
CLASS zcl_order_facade IMPLEMENTATION. METHOD constructor. " Initialiser les services internes mo_validator = NEW zcl_order_validator( ). mo_inventory = NEW zcl_inventory_service( ). mo_payment = NEW zcl_payment_service( ). mo_notification = NEW zcl_notification_service( ). mo_logger = NEW zcl_order_logger( ). ENDMETHOD.
METHOD process_complete_order. " Processus complexe derriere une interface simple mo_logger->log_start( is_order ).
" 1. Validation DATA(lv_valid) = mo_validator->validate_order( is_order ). IF lv_valid = abap_false. RAISE EXCEPTION TYPE cx_order_processing MESSAGE e001(zorder) WITH is_order-order_id. ENDIF.
" 2. Verification et reservation du stock DATA(lv_available) = mo_inventory->check_availability( is_order-items ). IF lv_available = abap_false. RAISE EXCEPTION TYPE cx_order_processing MESSAGE e002(zorder). ENDIF. mo_inventory->reserve_items( is_order-items ).
" 3. Traitement du paiement TRY. DATA(ls_payment) = mo_payment->process_payment( iv_amount = is_order-total_amount is_payment_method = is_order-payment_method ). CATCH cx_payment_failed INTO DATA(lx_payment). " Annuler la reservation mo_inventory->release_reservation( is_order-items ). RAISE EXCEPTION TYPE cx_order_processing MESSAGE e003(zorder) PREVIOUS lx_payment. ENDTRY.
" 4. Finaliser la commande rs_result-order_id = zcl_number_range=>get_next( 'ZORDER' ). rs_result-status = 'CONFIRMED'. rs_result-payment_ref = ls_payment-reference. rs_result-created_at = utclong_current( ).
" 5. Notifications mo_notification->send_confirmation( iv_email = is_order-customer_email is_order = rs_result ).
mo_logger->log_success( rs_result ). ENDMETHOD.
METHOD cancel_order. mo_logger->log_cancellation_start( iv_order_id ).
" Processus d'annulation mo_inventory->release_reservation( iv_order_id ). mo_payment->refund( iv_order_id ). mo_notification->send_cancellation( iv_order_id ).
rv_success = abap_true. mo_logger->log_cancellation_success( iv_order_id ). ENDMETHOD.ENDCLASS.Utilisation dans une RAP Action
CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS create_and_process FOR MODIFY IMPORTING keys FOR ACTION Order~createAndProcess RESULT result.
METHODS cancel FOR MODIFY IMPORTING keys FOR ACTION Order~cancel RESULT result.ENDCLASS.
CLASS lhc_order IMPLEMENTATION. METHOD create_and_process. DATA(lo_facade) = NEW zcl_order_facade( ).
LOOP AT keys INTO DATA(ls_key). TRY. " Un appel - processus complet DATA(ls_result) = lo_facade->process_complete_order( is_order = ls_key-%param ).
" Retourner le succes APPEND VALUE #( %cid = ls_key-%cid %param = ls_result ) TO result.
CATCH cx_order_processing INTO DATA(lx_error). " Afficher l'erreur dans Reported APPEND VALUE #( %cid = ls_key-%cid ) TO failed-order. APPEND VALUE #( %cid = ls_key-%cid %msg = lx_error ) TO reported-order. ENDTRY. ENDLOOP. ENDMETHOD.
METHOD cancel. DATA(lo_facade) = NEW zcl_order_facade( ).
LOOP AT keys INTO DATA(ls_key). TRY. DATA(lv_success) = lo_facade->cancel_order( ls_key-OrderId ).
IF lv_success = abap_true. MODIFY ENTITIES OF zi_order IN LOCAL MODE ENTITY Order UPDATE FIELDS ( Status ) WITH VALUE #( ( %tky = ls_key-%tky Status = 'CANCELLED' ) ).
APPEND VALUE #( %tky = ls_key-%tky %param = VALUE #( success = abap_true ) ) TO result. ENDIF.
CATCH cx_order_processing INTO DATA(lx_error). APPEND VALUE #( %tky = ls_key-%tky ) TO failed-order. APPEND VALUE #( %tky = ls_key-%tky %msg = lx_error ) TO reported-order. ENDTRY. ENDLOOP. ENDMETHOD.ENDCLASS.Quand utiliser quel Pattern ?
| Situation | Pattern | Exemple |
|---|---|---|
| Creation d’objets dependant de parametres | Factory | Calcul de prix par pays |
| Algorithme interchangeable | Strategy | Strategies de remise |
| Meme flux, details differents | Template Method | Generation de documents |
| Simplifier un sous-systeme complexe | Facade | Traitement de commandes |
Eviter les anti-patterns
1. God Class - trop de responsabilites
" MAUVAIS : Une classe fait toutCLASS zcl_order_manager DEFINITION. PUBLIC SECTION. METHODS validate_order. METHODS calculate_price. METHODS calculate_tax. METHODS apply_discount. METHODS reserve_inventory. METHODS process_payment. METHODS send_email. METHODS generate_pdf. METHODS update_statistics.ENDCLASS.
" BON : Repartir les responsabilitesCLASS zcl_order_validator DEFINITION. " Seulement la validationCLASS zcl_price_calculator DEFINITION. " Seulement le calcul de prixCLASS zcl_inventory_service DEFINITION. " Seulement le stockCLASS zcl_order_facade DEFINITION. " Orchestration2. Dependances codees en dur
" MAUVAIS : Instanciation directeMETHOD process. DATA(lo_service) = NEW zcl_payment_service( ). " Non testable lo_service->process( ).ENDMETHOD.
" BON : Injection de dependancesMETHOD constructor IMPORTING io_payment_service TYPE REF TO zif_payment_service. mo_payment_service = io_payment_service.ENDMETHOD.3. Instructions Switch au lieu du polymorphisme
" MAUVAIS : Switch pour les typesMETHOD get_discount. CASE iv_customer_type. WHEN 'VIP'. rv_discount = 15. WHEN 'REGULAR'. rv_discount = 5. WHEN 'NEW'. rv_discount = 10. ENDCASE.ENDMETHOD.
" BON : Pattern StrategyMETHOD get_discount. rv_discount = mo_discount_strategy->calculate( is_customer ).ENDMETHOD.Bonnes pratiques pour RAP
- Interfaces pour la flexibilite : Definir des interfaces pour tous les composants interchangeables
- Classes petites et ciblees : Chaque classe a une responsabilite
- Injection de dependances : Injecter les dependances via le constructeur ou une Factory
- Testabilite : Les patterns permettent d’utiliser des objets mock
- Documentation : Mentionner les noms de patterns dans le code (
" Strategy Pattern)
Combinaison de Patterns
En pratique, les patterns sont souvent combines :
" Combinaison Factory + StrategyCLASS zcl_discount_factory DEFINITION. PUBLIC SECTION. CLASS-METHODS create_strategy IMPORTING iv_campaign TYPE string RETURNING VALUE(ro_strategy) TYPE REF TO zif_discount_strategy.ENDCLASS.
CLASS zcl_discount_factory IMPLEMENTATION. METHOD create_strategy. CASE iv_campaign. WHEN 'SUMMER'. ro_strategy = NEW zcl_percentage_discount( 25 ). WHEN 'VIP'. ro_strategy = NEW zcl_vip_discount( ). WHEN OTHERS. ro_strategy = NEW zcl_no_discount( ). ENDCASE. ENDMETHOD.ENDCLASS.Sujets connexes
- RAP Grundlagen - Base pour le developpement de Business Objects
- Clean ABAP - Qualite du code et conventions de nommage
- EML Entity Manipulation Language - Operations de donnees dans RAP
- RAP Actions und Functions - Exposer la logique metier
Conclusion
Les Design Patterns ne sont pas une fin en soi, mais des outils pour un meilleur code. Dans RAP, ils aident particulierement a :
- Flexibilite : Implementations interchangeables sans modifier les appelants
- Testabilite : Objets mock au lieu de vrais services dans les tests unitaires
- Maintenabilite : Classes petites et comprehensibles au lieu de code monolithique
- Extensibilite : Nouvelles variantes sans modifier le code existant
Commencez par le pattern le plus simple qui resout votre probleme. Toutes les situations ne necessitent pas des patterns complexes - parfois une simple instruction IF est la meilleure solution.