Le Factory Pattern est l’un des patterns de création (Creational Patterns) les plus importants en programmation orientée objet. Il encapsule la création d’objets et découple le code appelant des implémentations concrètes. En ABAP Cloud, il est particulièrement précieux pour des applications RAP flexibles et testables.
Pourquoi les Factory Patterns ?
| Problème | Solution par Factory |
|---|---|
| Instanciation de classes codée en dur | Logique de création centralisée |
| Code difficile à tester | Objets mock facilement injectables |
| Pas de flexibilité pour changer de variante | Décision possible à l’exécution |
| Logique de création dupliquée | Single Point of Creation |
| Couplage fort aux classes concrètes | Programmation contre des interfaces |
Les trois variantes de Factory
Il existe trois variantes principales du Factory Pattern qui diffèrent par leur complexité et leur cas d’utilisation :
| Variante | Complexité | Cas d’utilisation |
|---|---|---|
| Simple Factory | Faible | Une famille de produits, création statique |
| Factory Method | Moyenne | Hiérarchie d’héritage, sous-classes déterminent le type |
| Abstract Factory | Élevée | Plusieurs familles de produits, objets connexes |
Simple Factory
La Simple Factory n’est techniquement pas un pattern GoF, mais c’est la variante la plus courante et pratique. Une classe factory avec une méthode statique crée des objets basés sur des paramètres.
Scénario : Stratégies de paiement
" Interface pour le traitement des paiementsINTERFACE zif_payment_processor. METHODS process IMPORTING iv_amount TYPE decfloat34 RETURNING VALUE(rs_result) TYPE zpayment_result RAISING cx_payment_error.
METHODS get_fee IMPORTING iv_amount TYPE decfloat34 RETURNING VALUE(rv_fee) TYPE decfloat34.ENDINTERFACE.
" Implémentation : Carte de créditCLASS zcl_credit_card_processor DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_payment_processor.
ENDCLASS.
CLASS zcl_credit_card_processor IMPLEMENTATION. METHOD zif_payment_processor~process. " Traitement spécifique carte de crédit " Validation, 3D Secure, appel gateway rs_result-payment_id = cl_system_uuid=>create_uuid_x16_static( ). rs_result-status = 'SUCCESS'. rs_result-fee = iv_amount * '0.029'. " 2.9% de frais ENDMETHOD.
METHOD zif_payment_processor~get_fee. rv_fee = iv_amount * '0.029'. ENDMETHOD.ENDCLASS.
" Implémentation : PayPalCLASS zcl_paypal_processor DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_payment_processor.
ENDCLASS.
CLASS zcl_paypal_processor IMPLEMENTATION. METHOD zif_payment_processor~process. " Appel API PayPal rs_result-payment_id = cl_system_uuid=>create_uuid_x16_static( ). rs_result-status = 'SUCCESS'. rs_result-fee = iv_amount * '0.0249' + '0.35'. " 2.49% + 0.35 EUR ENDMETHOD.
METHOD zif_payment_processor~get_fee. rv_fee = iv_amount * '0.0249' + '0.35'. ENDMETHOD.ENDCLASS.
" Implémentation : Virement bancaireCLASS zcl_bank_transfer_processor DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_payment_processor.
ENDCLASS.
CLASS zcl_bank_transfer_processor IMPLEMENTATION. METHOD zif_payment_processor~process. " Pas de traitement en ligne, seulement instruction de paiement rs_result-payment_id = cl_system_uuid=>create_uuid_x16_static( ). rs_result-status = 'PENDING'. rs_result-fee = '0.00'. " Pas de frais ENDMETHOD.
METHOD zif_payment_processor~get_fee. rv_fee = '0.00'. ENDMETHOD.ENDCLASS.Classe Simple Factory
CLASS zcl_payment_factory DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION. TYPES: BEGIN OF ty_payment_method, method_id TYPE string, description TYPE string, END OF ty_payment_method, tt_payment_methods TYPE STANDARD TABLE OF ty_payment_method WITH KEY method_id.
" Méthode Factory : crée l'implémentation appropriée CLASS-METHODS create IMPORTING iv_payment_method TYPE string RETURNING VALUE(ro_processor) TYPE REF TO zif_payment_processor RAISING cx_invalid_payment_method.
" Méthode utilitaire : Modes de paiement disponibles CLASS-METHODS get_available_methods RETURNING VALUE(rt_methods) TYPE tt_payment_methods.
ENDCLASS.
CLASS zcl_payment_factory IMPLEMENTATION. METHOD create. CASE to_upper( iv_payment_method ). WHEN 'CREDIT_CARD'. ro_processor = NEW zcl_credit_card_processor( ).
WHEN 'PAYPAL'. ro_processor = NEW zcl_paypal_processor( ).
WHEN 'BANK_TRANSFER'. ro_processor = NEW zcl_bank_transfer_processor( ).
WHEN 'INVOICE'. ro_processor = NEW zcl_invoice_processor( ).
WHEN OTHERS. RAISE EXCEPTION TYPE cx_invalid_payment_method MESSAGE e001(zpayment) WITH iv_payment_method. ENDCASE. ENDMETHOD.
METHOD get_available_methods. rt_methods = VALUE #( ( method_id = 'CREDIT_CARD' description = 'Carte de crédit' ) ( method_id = 'PAYPAL' description = 'PayPal' ) ( method_id = 'BANK_TRANSFER' description = 'Virement bancaire' ) ( method_id = 'INVOICE' description = 'Facture' ) ). ENDMETHOD.ENDCLASS.Utilisation dans une Action RAP
CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS process_payment FOR MODIFY IMPORTING keys FOR ACTION Order~processPayment RESULT result.ENDCLASS.
CLASS lhc_order IMPLEMENTATION. METHOD process_payment. READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order FIELDS ( PaymentMethod TotalAmount ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order). TRY. " La Factory crée le bon Processor DATA(lo_processor) = zcl_payment_factory=>create( iv_payment_method = ls_order-PaymentMethod ).
" Appel uniforme - indépendant de la classe concrète DATA(ls_payment_result) = lo_processor->process( iv_amount = ls_order-TotalAmount ).
" Mettre à jour la commande MODIFY ENTITIES OF zi_order IN LOCAL MODE ENTITY Order UPDATE FIELDS ( PaymentId PaymentStatus PaymentFee ) WITH VALUE #( ( %tky = ls_order-%tky PaymentId = ls_payment_result-payment_id PaymentStatus = ls_payment_result-status PaymentFee = ls_payment_result-fee ) ).
APPEND VALUE #( %tky = ls_order-%tky %param = CORRESPONDING #( ls_payment_result ) ) TO result.
CATCH cx_invalid_payment_method cx_payment_error INTO DATA(lx_error). APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order. APPEND VALUE #( %tky = ls_order-%tky %msg = lx_error ) TO reported-order. ENDTRY. ENDLOOP. ENDMETHOD.ENDCLASS.Factory Method Pattern
Le Factory Method Pattern utilise l’héritage : une classe de base abstraite définit une méthode factory qui est implémentée par les sous-classes. Chaque sous-classe crée sa variante de produit spécifique.
Scénario : Générateurs de documents par type
" Classe de base abstraite avec Factory MethodCLASS zcl_document_creator DEFINITION PUBLIC ABSTRACT CREATE PUBLIC.
PUBLIC SECTION. " Template Method : Utilise la Factory Method METHODS create_and_send FINAL IMPORTING is_order TYPE zorder RETURNING VALUE(rv_success) TYPE abap_bool RAISING cx_document_error.
PROTECTED SECTION. " Factory Method - implémentée par les sous-classes METHODS create_document ABSTRACT IMPORTING is_order TYPE zorder RETURNING VALUE(ro_document) TYPE REF TO zif_document RAISING cx_document_error.
" Méthode hook avec implémentation par défaut METHODS get_recipient IMPORTING is_order TYPE zorder RETURNING VALUE(rv_recipient) TYPE string.
ENDCLASS.
CLASS zcl_document_creator IMPLEMENTATION. METHOD create_and_send. " Appeler la Factory Method DATA(lo_document) = create_document( is_order ).
" Envoyer le document DATA(lv_recipient) = get_recipient( is_order ). lo_document->send( iv_recipient = lv_recipient ).
rv_success = abap_true. ENDMETHOD.
METHOD get_recipient. rv_recipient = is_order-customer_email. ENDMETHOD.ENDCLASS.Sous-classes Creator concrètes
" Creator pour les facturesCLASS zcl_invoice_creator DEFINITION INHERITING FROM zcl_document_creator PUBLIC FINAL CREATE PUBLIC.
PROTECTED SECTION. METHODS create_document REDEFINITION. METHODS get_recipient REDEFINITION.
ENDCLASS.
CLASS zcl_invoice_creator IMPLEMENTATION. METHOD create_document. " Créer une facture DATA(lo_invoice) = NEW zcl_invoice( ).
lo_invoice->set_header( iv_invoice_number = is_order-order_id iv_date = sy-datum iv_customer = is_order-customer_name ).
LOOP AT is_order-items INTO DATA(ls_item). lo_invoice->add_line_item( iv_description = ls_item-description iv_quantity = ls_item-quantity iv_unit_price = ls_item-price ). ENDLOOP.
lo_invoice->calculate_totals( ).
ro_document = lo_invoice. ENDMETHOD.
METHOD get_recipient. " Les factures vont à l'email de facturation IF is_order-billing_email IS NOT INITIAL. rv_recipient = is_order-billing_email. ELSE. rv_recipient = super->get_recipient( is_order ). ENDIF. ENDMETHOD.ENDCLASS.
" Creator pour les bons de livraisonCLASS zcl_delivery_note_creator DEFINITION INHERITING FROM zcl_document_creator PUBLIC FINAL CREATE PUBLIC.
PROTECTED SECTION. METHODS create_document REDEFINITION.
ENDCLASS.
CLASS zcl_delivery_note_creator IMPLEMENTATION. METHOD create_document. " Créer un bon de livraison DATA(lo_delivery) = NEW zcl_delivery_note( ).
lo_delivery->set_header( iv_delivery_number = |LF-{ is_order-order_id }| iv_shipping_date = is_order-shipping_date iv_address = is_order-shipping_address ).
LOOP AT is_order-items INTO DATA(ls_item). lo_delivery->add_item( iv_product_id = ls_item-product_id iv_description = ls_item-description iv_quantity = ls_item-quantity ). ENDLOOP.
ro_document = lo_delivery. ENDMETHOD.ENDCLASS.
" Creator pour les confirmations de commandeCLASS zcl_confirmation_creator DEFINITION INHERITING FROM zcl_document_creator PUBLIC FINAL CREATE PUBLIC.
PROTECTED SECTION. METHODS create_document REDEFINITION.
ENDCLASS.
CLASS zcl_confirmation_creator IMPLEMENTATION. METHOD create_document. " Créer une confirmation de commande DATA(lo_confirmation) = NEW zcl_order_confirmation( ).
lo_confirmation->set_order( is_order ). lo_confirmation->set_estimated_delivery( iv_days = is_order-delivery_time_days ).
ro_document = lo_confirmation. ENDMETHOD.ENDCLASS.Meta-Factory pour la sélection du Creator
" Factory pour la sélection du CreatorCLASS zcl_document_creator_factory DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION. CLASS-METHODS get_creator IMPORTING iv_document_type TYPE string RETURNING VALUE(ro_creator) TYPE REF TO zcl_document_creator RAISING cx_invalid_document_type.
ENDCLASS.
CLASS zcl_document_creator_factory IMPLEMENTATION. METHOD get_creator. CASE iv_document_type. WHEN 'INVOICE'. ro_creator = NEW zcl_invoice_creator( ). WHEN 'DELIVERY_NOTE'. ro_creator = NEW zcl_delivery_note_creator( ). WHEN 'CONFIRMATION'. ro_creator = NEW zcl_confirmation_creator( ). WHEN OTHERS. RAISE EXCEPTION TYPE cx_invalid_document_type MESSAGE e001(zdocument) WITH iv_document_type. ENDCASE. ENDMETHOD.ENDCLASS.Utilisation dans RAP
METHOD generate_documents. 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). " Générer tous les documents nécessaires DATA(lt_doc_types) = VALUE string_table( ( |CONFIRMATION| ) ( |INVOICE| ) ( |DELIVERY_NOTE| ) ).
LOOP AT lt_doc_types INTO DATA(lv_doc_type). TRY. " Factory Method Pattern en action DATA(lo_creator) = zcl_document_creator_factory=>get_creator( iv_document_type = lv_doc_type ).
" La Template Method exécute la Factory Method lo_creator->create_and_send( is_order = CORRESPONDING #( ls_order ) ).
CATCH cx_document_error INTO DATA(lx_error). " Logger l'erreur mais continuer NEW zcl_application_logger( )->log_error( lx_error ). ENDTRY. ENDLOOP. ENDLOOP.ENDMETHOD.Abstract Factory Pattern
L’Abstract Factory Pattern est le Factory Pattern le plus complexe. Il définit une interface pour créer des familles d’objets connexes sans spécifier leurs classes concrètes.
Scénario : Composants UI pour différents thèmes
" Produits abstraitsINTERFACE zif_button. METHODS render RETURNING VALUE(rv_html) TYPE string. METHODS set_label IMPORTING iv_label TYPE string.ENDINTERFACE.
INTERFACE zif_input_field. METHODS render RETURNING VALUE(rv_html) TYPE string. METHODS set_placeholder IMPORTING iv_placeholder TYPE string.ENDINTERFACE.
INTERFACE zif_card. METHODS render RETURNING VALUE(rv_html) TYPE string. METHODS set_title IMPORTING iv_title TYPE string. METHODS set_content IMPORTING iv_content TYPE string.ENDINTERFACE.Interface Abstract Factory
" Interface Abstract FactoryINTERFACE zif_ui_factory. METHODS create_button RETURNING VALUE(ro_button) TYPE REF TO zif_button.
METHODS create_input_field RETURNING VALUE(ro_input) TYPE REF TO zif_input_field.
METHODS create_card RETURNING VALUE(ro_card) TYPE REF TO zif_card.ENDINTERFACE.Factories concrètes pour différents thèmes
" ===== Thème SAP Fiori =====CLASS zcl_fiori_button DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_button. PRIVATE SECTION. DATA mv_label TYPE string.ENDCLASS.
CLASS zcl_fiori_button IMPLEMENTATION. METHOD zif_button~render. rv_html = |<button class="sapMBtn">{ mv_label }</button>|. ENDMETHOD. METHOD zif_button~set_label. mv_label = iv_label. ENDMETHOD.ENDCLASS.
CLASS zcl_fiori_input DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_input_field. PRIVATE SECTION. DATA mv_placeholder TYPE string.ENDCLASS.
CLASS zcl_fiori_input IMPLEMENTATION. METHOD zif_input_field~render. rv_html = |<input class="sapMInput" placeholder="{ mv_placeholder }"/>|. ENDMETHOD. METHOD zif_input_field~set_placeholder. mv_placeholder = iv_placeholder. ENDMETHOD.ENDCLASS.
CLASS zcl_fiori_card DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_card. PRIVATE SECTION. DATA mv_title TYPE string. DATA mv_content TYPE string.ENDCLASS.
CLASS zcl_fiori_card IMPLEMENTATION. METHOD zif_card~render. rv_html = |<div class="sapFCard"><h2>{ mv_title }</h2>| && |<div class="sapFCardContent">{ mv_content }</div></div>|. ENDMETHOD. METHOD zif_card~set_title. mv_title = iv_title. ENDMETHOD. METHOD zif_card~set_content. mv_content = iv_content. ENDMETHOD.ENDCLASS.
" Fiori FactoryCLASS zcl_fiori_ui_factory DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_ui_factory.ENDCLASS.
CLASS zcl_fiori_ui_factory IMPLEMENTATION. METHOD zif_ui_factory~create_button. ro_button = NEW zcl_fiori_button( ). ENDMETHOD. METHOD zif_ui_factory~create_input_field. ro_input = NEW zcl_fiori_input( ). ENDMETHOD. METHOD zif_ui_factory~create_card. ro_card = NEW zcl_fiori_card( ). ENDMETHOD.ENDCLASS.
" ===== Thème Bootstrap =====CLASS zcl_bootstrap_button DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_button. PRIVATE SECTION. DATA mv_label TYPE string.ENDCLASS.
CLASS zcl_bootstrap_button IMPLEMENTATION. METHOD zif_button~render. rv_html = |<button class="btn btn-primary">{ mv_label }</button>|. ENDMETHOD. METHOD zif_button~set_label. mv_label = iv_label. ENDMETHOD.ENDCLASS.
CLASS zcl_bootstrap_input DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_input_field. PRIVATE SECTION. DATA mv_placeholder TYPE string.ENDCLASS.
CLASS zcl_bootstrap_input IMPLEMENTATION. METHOD zif_input_field~render. rv_html = |<input class="form-control" placeholder="{ mv_placeholder }"/>|. ENDMETHOD. METHOD zif_input_field~set_placeholder. mv_placeholder = iv_placeholder. ENDMETHOD.ENDCLASS.
CLASS zcl_bootstrap_card DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_card. PRIVATE SECTION. DATA mv_title TYPE string. DATA mv_content TYPE string.ENDCLASS.
CLASS zcl_bootstrap_card IMPLEMENTATION. METHOD zif_card~render. rv_html = |<div class="card"><div class="card-header">{ mv_title }</div>| && |<div class="card-body">{ mv_content }</div></div>|. ENDMETHOD. METHOD zif_card~set_title. mv_title = iv_title. ENDMETHOD. METHOD zif_card~set_content. mv_content = iv_content. ENDMETHOD.ENDCLASS.
" Bootstrap FactoryCLASS zcl_bootstrap_ui_factory DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_ui_factory.ENDCLASS.
CLASS zcl_bootstrap_ui_factory IMPLEMENTATION. METHOD zif_ui_factory~create_button. ro_button = NEW zcl_bootstrap_button( ). ENDMETHOD. METHOD zif_ui_factory~create_input_field. ro_input = NEW zcl_bootstrap_input( ). ENDMETHOD. METHOD zif_ui_factory~create_card. ro_card = NEW zcl_bootstrap_card( ). ENDMETHOD.ENDCLASS.Factory Provider
" Provider pour Abstract FactoryCLASS zcl_ui_factory_provider DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION. CLASS-METHODS get_factory IMPORTING iv_theme TYPE string DEFAULT 'FIORI" RETURNING VALUE(ro_factory) TYPE REF TO zif_ui_factory.
ENDCLASS.
CLASS zcl_ui_factory_provider IMPLEMENTATION. METHOD get_factory. CASE to_upper( iv_theme ). WHEN 'FIORI'. ro_factory = NEW zcl_fiori_ui_factory( ). WHEN 'BOOTSTRAP'. ro_factory = NEW zcl_bootstrap_ui_factory( ). WHEN 'MATERIAL'. ro_factory = NEW zcl_material_ui_factory( ). WHEN OTHERS. ro_factory = NEW zcl_fiori_ui_factory( ). ENDCASE. ENDMETHOD.ENDCLASS.Code client
CLASS zcl_form_builder DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS constructor IMPORTING io_factory TYPE REF TO zif_ui_factory.
METHODS build_login_form RETURNING VALUE(rv_html) TYPE string.
PRIVATE SECTION. DATA mo_factory TYPE REF TO zif_ui_factory.
ENDCLASS.
CLASS zcl_form_builder IMPLEMENTATION. METHOD constructor. mo_factory = io_factory. ENDMETHOD.
METHOD build_login_form. " Tous les composants de la même famille Factory DATA(lo_card) = mo_factory->create_card( ). DATA(lo_username) = mo_factory->create_input_field( ). DATA(lo_password) = mo_factory->create_input_field( ). DATA(lo_button) = mo_factory->create_button( ).
lo_card->set_title( 'Connexion' ). lo_username->set_placeholder( 'Nom d''utilisateur' ). lo_password->set_placeholder( 'Mot de passe' ). lo_button->set_label( 'Se connecter' ).
" Assembler DATA(lv_form_content) = lo_username->render( ) && lo_password->render( ) && lo_button->render( ).
lo_card->set_content( lv_form_content ). rv_html = lo_card->render( ). ENDMETHOD.ENDCLASS.
" UtilisationDATA(lo_factory) = zcl_ui_factory_provider=>get_factory( 'FIORI' ).DATA(lo_builder) = NEW zcl_form_builder( lo_factory ).DATA(lv_html) = lo_builder->build_login_form( ).Factory avec Dependency Injection
La Factory est parfaitement adaptée pour le Dependency Injection, surtout dans RAP où l’injection directe via constructeur n’est pas possible.
Service Factory pour RAP
" Interface pour tous les servicesINTERFACE zif_order_service. METHODS validate_order IMPORTING is_order TYPE zorder RAISING cx_validation_error.
METHODS calculate_totals IMPORTING is_order TYPE zorder RETURNING VALUE(rs_totals) TYPE zorder_totals.ENDINTERFACE.
INTERFACE zif_inventory_service. METHODS check_availability IMPORTING it_items TYPE zorder_items RETURNING VALUE(rv_available) TYPE abap_bool.
METHODS reserve_items IMPORTING it_items TYPE zorder_items.ENDINTERFACE.
INTERFACE zif_notification_service. METHODS send_confirmation IMPORTING iv_email TYPE string is_order TYPE zorder.ENDINTERFACE.Service Factory avec mode Production et Test
CLASS zcl_service_factory DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION. " Accès Singleton CLASS-METHODS get_instance RETURNING VALUE(ro_instance) TYPE REF TO zcl_service_factory.
" Getters de service METHODS get_order_service RETURNING VALUE(ro_service) TYPE REF TO zif_order_service.
METHODS get_inventory_service RETURNING VALUE(ro_service) TYPE REF TO zif_inventory_service.
METHODS get_notification_service RETURNING VALUE(ro_service) TYPE REF TO zif_notification_service.
" Pour les tests unitaires : Injection de service METHODS set_order_service IMPORTING io_service TYPE REF TO zif_order_service.
METHODS set_inventory_service IMPORTING io_service TYPE REF TO zif_inventory_service.
METHODS set_notification_service IMPORTING io_service TYPE REF TO zif_notification_service.
" Réinitialiser le mode test METHODS reset_to_production.
PRIVATE SECTION. CLASS-DATA go_instance TYPE REF TO zcl_service_factory.
DATA mo_order_service TYPE REF TO zif_order_service. DATA mo_inventory_service TYPE REF TO zif_inventory_service. DATA mo_notification_service TYPE REF TO zif_notification_service.
ENDCLASS.
CLASS zcl_service_factory IMPLEMENTATION. METHOD get_instance. IF go_instance IS NOT BOUND. go_instance = NEW zcl_service_factory( ). ENDIF. ro_instance = go_instance. ENDMETHOD.
METHOD get_order_service. IF mo_order_service IS NOT BOUND. " Implémentation standard mo_order_service = NEW zcl_order_service( ). ENDIF. ro_service = mo_order_service. ENDMETHOD.
METHOD get_inventory_service. IF mo_inventory_service IS NOT BOUND. mo_inventory_service = NEW zcl_inventory_service( ). ENDIF. ro_service = mo_inventory_service. ENDMETHOD.
METHOD get_notification_service. IF mo_notification_service IS NOT BOUND. mo_notification_service = NEW zcl_notification_service( ). ENDIF. ro_service = mo_notification_service. ENDMETHOD.
METHOD set_order_service. mo_order_service = io_service. ENDMETHOD.
METHOD set_inventory_service. mo_inventory_service = io_service. ENDMETHOD.
METHOD set_notification_service. mo_notification_service = io_service. ENDMETHOD.
METHOD reset_to_production. CLEAR: mo_order_service, mo_inventory_service, mo_notification_service. ENDMETHOD.ENDCLASS.Utilisation dans RAP Behavior Handler
CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS validate_order FOR VALIDATE ON SAVE IMPORTING keys FOR Order~validateOrder.
METHODS process_order FOR MODIFY IMPORTING keys FOR ACTION Order~processOrder RESULT result.
" Helper : Accès Factory METHODS get_factory RETURNING VALUE(ro_factory) TYPE REF TO zcl_service_factory.ENDCLASS.
CLASS lhc_order IMPLEMENTATION. METHOD get_factory. ro_factory = zcl_service_factory=>get_instance( ). ENDMETHOD.
METHOD validate_order. READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
DATA(lo_service) = get_factory( )->get_order_service( ).
LOOP AT lt_orders INTO DATA(ls_order). TRY. lo_service->validate_order( is_order = CORRESPONDING #( ls_order ) ). CATCH cx_validation_error INTO DATA(lx_error). APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order. APPEND VALUE #( %tky = ls_order-%tky %msg = lx_error ) TO reported-order. ENDTRY. ENDLOOP. ENDMETHOD.
METHOD process_order. READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
DATA(lo_factory) = get_factory( ).
LOOP AT lt_orders INTO DATA(ls_order). " Obtenir les services via Factory DATA(lo_inventory) = lo_factory->get_inventory_service( ). DATA(lo_notification) = lo_factory->get_notification_service( ).
" Vérifier le stock DATA(lv_available) = lo_inventory->check_availability( it_items = ls_order-Items ).
IF lv_available = abap_true. lo_inventory->reserve_items( ls_order-Items ).
" Envoyer la confirmation lo_notification->send_confirmation( iv_email = ls_order-CustomerEmail is_order = CORRESPONDING #( ls_order ) ).
MODIFY ENTITIES OF zi_order IN LOCAL MODE ENTITY Order UPDATE FIELDS ( Status ) WITH VALUE #( ( %tky = ls_order-%tky Status = 'CONFIRMED' ) ). ENDIF.
APPEND VALUE #( %tky = ls_order-%tky %param = VALUE #( success = lv_available ) ) TO result. ENDLOOP. ENDMETHOD.ENDCLASS.Test unitaire avec Mock-Services
CLASS ltcl_order_handler DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
PRIVATE SECTION. DATA mo_factory TYPE REF TO zcl_service_factory.
METHODS setup. METHODS teardown.
METHODS test_process_order_success FOR TESTING. METHODS test_process_order_no_stock FOR TESTING.ENDCLASS.
CLASS ltcl_order_handler IMPLEMENTATION. METHOD setup. mo_factory = zcl_service_factory=>get_instance( ). ENDMETHOD.
METHOD teardown. mo_factory->reset_to_production( ). ENDMETHOD.
METHOD test_process_order_success. " Injecter les Mock-Services DATA(lo_mock_inventory) = NEW lcl_mock_inventory( ). lo_mock_inventory->set_availability( abap_true ). mo_factory->set_inventory_service( lo_mock_inventory ).
DATA(lo_mock_notification) = NEW lcl_mock_notification( ). mo_factory->set_notification_service( lo_mock_notification ).
" Exécuter le test... " Le handler utilise maintenant les Mock-Services
" Assertions cl_abap_unit_assert=>assert_true( lo_mock_notification->was_called( ) ). ENDMETHOD.
METHOD test_process_order_no_stock. " Mock sans stock DATA(lo_mock_inventory) = NEW lcl_mock_inventory( ). lo_mock_inventory->set_availability( abap_false ). mo_factory->set_inventory_service( lo_mock_inventory ).
" Exécuter le test...
" La notification ne devrait PAS avoir été appelée DATA(lo_mock_notification) = NEW lcl_mock_notification( ). mo_factory->set_notification_service( lo_mock_notification ).
cl_abap_unit_assert=>assert_false( lo_mock_notification->was_called( ) ). ENDMETHOD.ENDCLASS.
" Implémentation MockCLASS lcl_mock_inventory DEFINITION. PUBLIC SECTION. INTERFACES zif_inventory_service.
METHODS set_availability IMPORTING iv_available TYPE abap_bool.
PRIVATE SECTION. DATA mv_available TYPE abap_bool.ENDCLASS.
CLASS lcl_mock_inventory IMPLEMENTATION. METHOD set_availability. mv_available = iv_available. ENDMETHOD.
METHOD zif_inventory_service~check_availability. rv_available = mv_available. ENDMETHOD.
METHOD zif_inventory_service~reserve_items. " Mock - ne rien faire ENDMETHOD.ENDCLASS.Quand utiliser quel pattern ?
| Situation | Pattern recommandé | Justification |
|---|---|---|
| Création basée sur paramètre | Simple Factory | Simple, centralisé, facile à comprendre |
| Hiérarchie d’héritage avec produits | Factory Method | Les sous-classes déterminent les types concrets |
| Familles de produits connexes | Abstract Factory | Cohérence entre objets apparentés |
| Testabilité critique | Simple Factory + DI | Injection de Mock au moment du test |
| Peu de variantes | Simple Factory | Garder un overhead minimal |
| Nombreuses variantes avec points communs | Factory Method | Utiliser l’héritage |
Bonnes pratiques
1. Design Interface-First
" BIEN : Toujours programmer contre des interfacesDATA lo_service TYPE REF TO zif_payment_service.lo_service = zcl_payment_factory=>create( 'PAYPAL' ).
" MAL : Utiliser une classe concrèteDATA lo_paypal TYPE REF TO zcl_paypal_service.lo_paypal = NEW zcl_paypal_service( ).2. Méthodes Factory au lieu d’instanciation directe
" BIEN : Utiliser une FactoryDATA(lo_document) = zcl_document_factory=>create( 'INVOICE' ).
" MAL : Instanciation directeCASE lv_type. WHEN 'INVOICE'. lo_document = NEW zcl_invoice( ). WHEN 'DELIVERY'. lo_document = NEW zcl_delivery_note( ).ENDCASE.3. Exception pour les types inconnus
METHOD create. CASE iv_type. WHEN 'A'. ro_result = NEW zcl_type_a( ). WHEN 'B'. ro_result = NEW zcl_type_b( ). WHEN OTHERS. " IMPORTANT : Ne jamais retourner NULL RAISE EXCEPTION TYPE cx_invalid_type MESSAGE e001(zfactory) WITH iv_type. ENDCASE.ENDMETHOD.4. Lazy Initialization pour les objets coûteux
METHOD get_heavy_service. IF mo_heavy_service IS NOT BOUND. " Créer seulement à la première demande mo_heavy_service = NEW zcl_heavy_service( ). ENDIF. ro_service = mo_heavy_service.ENDMETHOD.5. Documenter la Factory
"! Factory pour les processeurs de paiement"! <p>Crée le Payment-Processor approprié basé sur le mode de paiement.</p>"! @parameter iv_payment_method | CREDIT_CARD, PAYPAL, BANK_TRANSFER, INVOICE"! @parameter ro_processor | Le Processor créé"! @raising cx_invalid_payment_method | Pour un mode de paiement inconnuCLASS-METHODS create IMPORTING iv_payment_method TYPE string RETURNING VALUE(ro_processor) TYPE REF TO zif_payment_processor RAISING cx_invalid_payment_method.Erreurs courantes à éviter
1. Inflation de Factories
Ne pas créer une Factory pour chaque classe. Seulement si :
- Plusieurs implémentations existent
- La logique de création est complexe
- La testabilité est importante
2. Factories avec état
" MAL : Factory avec étatCLASS zcl_bad_factory. DATA mv_counter TYPE i.
METHOD create. mv_counter = mv_counter + 1. " Problématique en utilisation parallèle ENDMETHOD.ENDCLASS.
" BIEN : Factory sans étatCLASS zcl_good_factory. CLASS-METHODS create RETURNING VALUE(ro_result) TYPE REF TO zif_product.ENDCLASS.3. Trop de paramètres
" MAL : Explosion de paramètresMETHOD create IMPORTING iv_type iv_variant iv_region iv_customer iv_date. " Difficile à maintenirENDMETHOD.
" BIEN : Objet paramètreMETHOD create IMPORTING is_config TYPE zfactory_config.ENDMETHOD.Sujets connexes
- Design Patterns pour RAP - Autres patterns comme Strategy et Facade
- Clean ABAP - Qualité du code et conventions de nommage
- Instruction INTERFACE - Définir des interfaces en ABAP
- Test Doubles et Mocking - Tests unitaires avec objets Mock
Conclusion
Le Factory Pattern dans ses différentes variantes est un outil indispensable pour le développement ABAP Cloud :
- Simple Factory pour la plupart des cas d’utilisation : Un endroit centralisé pour la création d’objets
- Factory Method pour les hiérarchies d’héritage : Les sous-classes déterminent l’implémentation concrète
- Abstract Factory pour les familles de produits : Créer des objets connexes de manière cohérente
- Factory + Dependency Injection pour la testabilité : Injecter des objets Mock dans les tests unitaires
La clé est de choisir le pattern le plus simple qui résout le problème. Commencez avec Simple Factory et passez aux variantes plus complexes seulement si nécessaire.