Factory Pattern en ABAP Cloud : Création flexible d

Catégorie
Best Practices
Publié
Auteur
Johannes

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èmeSolution par Factory
Instanciation de classes codée en durLogique de création centralisée
Code difficile à testerObjets mock facilement injectables
Pas de flexibilité pour changer de varianteDécision possible à l’exécution
Logique de création dupliquéeSingle Point of Creation
Couplage fort aux classes concrètesProgrammation 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 :

VarianteComplexitéCas d’utilisation
Simple FactoryFaibleUne famille de produits, création statique
Factory MethodMoyenneHiérarchie d’héritage, sous-classes déterminent le type
Abstract FactoryÉlevéePlusieurs 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 paiements
INTERFACE 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édit
CLASS 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 : PayPal
CLASS 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 bancaire
CLASS 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 Method
CLASS 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 factures
CLASS 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 livraison
CLASS 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 commande
CLASS 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 Creator
CLASS 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 abstraits
INTERFACE 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 Factory
INTERFACE 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 Factory
CLASS 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 Factory
CLASS 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 Factory
CLASS 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.
" Utilisation
DATA(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 services
INTERFACE 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 Mock
CLASS 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 ?

SituationPattern recommandéJustification
Création basée sur paramètreSimple FactorySimple, centralisé, facile à comprendre
Hiérarchie d’héritage avec produitsFactory MethodLes sous-classes déterminent les types concrets
Familles de produits connexesAbstract FactoryCohérence entre objets apparentés
Testabilité critiqueSimple Factory + DIInjection de Mock au moment du test
Peu de variantesSimple FactoryGarder un overhead minimal
Nombreuses variantes avec points communsFactory MethodUtiliser l’héritage

Bonnes pratiques

1. Design Interface-First

" BIEN : Toujours programmer contre des interfaces
DATA lo_service TYPE REF TO zif_payment_service.
lo_service = zcl_payment_factory=>create( 'PAYPAL' ).
" MAL : Utiliser une classe concrète
DATA 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 Factory
DATA(lo_document) = zcl_document_factory=>create( 'INVOICE' ).
" MAL : Instanciation directe
CASE 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 inconnu
CLASS-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 état
CLASS 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 état
CLASS 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ètres
METHOD create
IMPORTING
iv_type iv_variant iv_region iv_customer iv_date.
" Difficile à maintenir
ENDMETHOD.
" BIEN : Objet paramètre
METHOD create
IMPORTING
is_config TYPE zfactory_config.
ENDMETHOD.

Sujets connexes

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.