Das Factory Pattern ist eines der wichtigsten Erzeugungsmuster (Creational Patterns) in der objektorientierten Programmierung. Es kapselt die Objekterzeugung und entkoppelt den aufrufenden Code von konkreten Implementierungen. In ABAP Cloud ist es besonders wertvoll fuer flexible, testbare RAP-Anwendungen.
Warum Factory Patterns?
| Problem | Loesung durch Factory |
|---|---|
| Hartkodierte Klasseninstanziierung | Zentrale Erzeugungslogik |
| Schwer testbarer Code | Mock-Objekte einfach injizierbar |
| Keine Flexibilitaet bei Variantenwechsel | Laufzeit-Entscheidung moeglich |
| Duplizierte Erzeugungslogik | Single Point of Creation |
| Enge Kopplung an konkrete Klassen | Programmieren gegen Interfaces |
Die drei Factory-Varianten
Es gibt drei Hauptvarianten des Factory Patterns, die sich in Komplexitaet und Anwendungsfall unterscheiden:
| Variante | Komplexitaet | Anwendungsfall |
|---|---|---|
| Simple Factory | Niedrig | Eine Produktfamilie, statische Erzeugung |
| Factory Method | Mittel | Vererbungshierarchie, Subklassen bestimmen Typ |
| Abstract Factory | Hoch | Mehrere Produktfamilien, zusammenhaengende Objekte |
Simple Factory
Die Simple Factory ist technisch gesehen kein GoF-Pattern, aber die haeufigste und praktischste Variante. Eine Factory-Klasse mit statischer Methode erzeugt Objekte basierend auf Parametern.
Szenario: Zahlungsarten-Strategien
" Interface fuer ZahlungsabwicklungINTERFACE 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.
" Implementierung: KreditkarteCLASS 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. " Kreditkarten-spezifische Verarbeitung " Validierung, 3D Secure, Gateway-Aufruf rs_result-payment_id = cl_system_uuid=>create_uuid_x16_static( ). rs_result-status = 'SUCCESS'. rs_result-fee = iv_amount * '0.029'. " 2.9% Gebuehr ENDMETHOD.
METHOD zif_payment_processor~get_fee. rv_fee = iv_amount * '0.029'. ENDMETHOD.ENDCLASS.
" Implementierung: 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. " PayPal API-Aufruf 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.
" Implementierung: BankueberweisungCLASS 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. " Keine Online-Verarbeitung, nur Zahlungsanweisung rs_result-payment_id = cl_system_uuid=>create_uuid_x16_static( ). rs_result-status = 'PENDING'. rs_result-fee = '0.00'. " Keine Gebuehr ENDMETHOD.
METHOD zif_payment_processor~get_fee. rv_fee = '0.00'. ENDMETHOD.ENDCLASS.Simple Factory Klasse
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.
" Factory-Methode: erzeugt passende Implementierung CLASS-METHODS create IMPORTING iv_payment_method TYPE string RETURNING VALUE(ro_processor) TYPE REF TO zif_payment_processor RAISING cx_invalid_payment_method.
" Hilfsmethode: Verfuegbare Zahlungsarten 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 = 'Kreditkarte' ) ( method_id = 'PAYPAL' description = 'PayPal' ) ( method_id = 'BANK_TRANSFER' description = 'Bankueberweisung' ) ( method_id = 'INVOICE' description = 'Rechnung' ) ). ENDMETHOD.ENDCLASS.Verwendung in RAP Action
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. " Factory erzeugt den richtigen Processor DATA(lo_processor) = zcl_payment_factory=>create( iv_payment_method = ls_order-PaymentMethod ).
" Einheitlicher Aufruf - unabhaengig von konkreter Klasse DATA(ls_payment_result) = lo_processor->process( iv_amount = ls_order-TotalAmount ).
" Bestellung aktualisieren 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
Das Factory Method Pattern verwendet Vererbung: Eine abstrakte Basisklasse definiert eine Factory-Methode, die von Unterklassen implementiert wird. Jede Unterklasse erzeugt ihre spezifische Produktvariante.
Szenario: Dokumentengeneratoren nach Typ
" Abstrakte Basisklasse mit Factory MethodCLASS zcl_document_creator DEFINITION PUBLIC ABSTRACT CREATE PUBLIC.
PUBLIC SECTION. " Template Method: Nutzt 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 - wird von Unterklassen implementiert METHODS create_document ABSTRACT IMPORTING is_order TYPE zorder RETURNING VALUE(ro_document) TYPE REF TO zif_document RAISING cx_document_error.
" Hook-Methode mit Default-Implementierung METHODS get_recipient IMPORTING is_order TYPE zorder RETURNING VALUE(rv_recipient) TYPE string.
ENDCLASS.
CLASS zcl_document_creator IMPLEMENTATION. METHOD create_and_send. " Factory Method aufrufen DATA(lo_document) = create_document( is_order ).
" Dokument versenden 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.Konkrete Creator-Unterklassen
" Creator fuer RechnungenCLASS 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. " Rechnung erzeugen 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. " Rechnungen gehen an Buchhaltungs-Email 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 fuer LieferscheineCLASS 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. " Lieferschein erzeugen 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 fuer AuftragsbestaetigungenCLASS 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. " Auftragsbestaetigung erzeugen 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 zur Creator-Auswahl
" Factory fuer Creator-AuswahlCLASS 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.Verwendung in 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). " Alle benoetigten Dokumente generieren 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 in Aktion DATA(lo_creator) = zcl_document_creator_factory=>get_creator( iv_document_type = lv_doc_type ).
" Template Method fuehrt Factory Method aus lo_creator->create_and_send( is_order = CORRESPONDING #( ls_order ) ).
CATCH cx_document_error INTO DATA(lx_error). " Fehler loggen, aber fortfahren NEW zcl_application_logger( )->log_error( lx_error ). ENDTRY. ENDLOOP. ENDLOOP.ENDMETHOD.Abstract Factory Pattern
Das Abstract Factory Pattern ist das komplexeste Factory Pattern. Es definiert eine Schnittstelle zur Erzeugung von Familien zusammengehoeriger Objekte, ohne deren konkrete Klassen zu spezifizieren.
Szenario: UI-Komponenten fuer verschiedene Themes
" Abstrakte ProdukteINTERFACE 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.Abstract Factory Interface
" Abstract Factory InterfaceINTERFACE 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.Konkrete Factories fuer verschiedene Themes
" ===== SAP Fiori Theme =====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.
" ===== Bootstrap Theme =====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 fuer 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.Client-Code
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. " Alle Komponenten aus derselben Factory-Familie 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( 'Anmeldung' ). lo_username->set_placeholder( 'Benutzername' ). lo_password->set_placeholder( 'Passwort' ). lo_button->set_label( 'Anmelden' ).
" Zusammenbauen 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.
" VerwendungDATA(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 mit Dependency Injection
Die Factory eignet sich hervorragend fuer Dependency Injection, besonders in RAP, wo direkte Constructor-Injection nicht moeglich ist.
Service Factory fuer RAP
" Interface fuer alle 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 mit Produktions- und Test-Modus
CLASS zcl_service_factory DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION. " Singleton-Zugriff CLASS-METHODS get_instance RETURNING VALUE(ro_instance) TYPE REF TO zcl_service_factory.
" Service-Getter 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.
" Fuer Unit Tests: Service-Injection 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.
" Test-Modus zuruecksetzen 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. " Standard-Implementierung 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.Verwendung in 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: Factory-Zugriff 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). " Services ueber Factory holen DATA(lo_inventory) = lo_factory->get_inventory_service( ). DATA(lo_notification) = lo_factory->get_notification_service( ).
" Bestand pruefen DATA(lv_available) = lo_inventory->check_availability( it_items = ls_order-Items ).
IF lv_available = abap_true. lo_inventory->reserve_items( ls_order-Items ).
" Bestaetigung senden 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.Unit Test mit 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. " Mock-Services injizieren 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 ).
" Test ausfuehren... " Der Handler verwendet jetzt die Mock-Services
" Assertions cl_abap_unit_assert=>assert_true( lo_mock_notification->was_called( ) ). ENDMETHOD.
METHOD test_process_order_no_stock. " Mock ohne Bestand DATA(lo_mock_inventory) = NEW lcl_mock_inventory( ). lo_mock_inventory->set_availability( abap_false ). mo_factory->set_inventory_service( lo_mock_inventory ).
" Test ausfuehren...
" Notification sollte NICHT gerufen worden sein 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.
" Mock-ImplementierungCLASS 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 - nichts tun ENDMETHOD.ENDCLASS.Wann welches Pattern verwenden?
| Situation | Empfohlenes Pattern | Begruendung |
|---|---|---|
| Erzeugung basierend auf Parameter | Simple Factory | Einfach, zentral, leicht verstaendlich |
| Vererbungshierarchie mit Produkten | Factory Method | Unterklassen bestimmen konkrete Typen |
| Zusammengehoerige Produktfamilien | Abstract Factory | Konsistenz zwischen verwandten Objekten |
| Testbarkeit ist kritisch | Simple Factory + DI | Mock-Injection zur Testzeit |
| Nur wenige Varianten | Simple Factory | Overhead gering halten |
| Viele Varianten mit Gemeinsamkeiten | Factory Method | Vererbung nutzen |
Best Practices
1. Interface-First Design
" GUT: Immer gegen Interfaces programmierenDATA lo_service TYPE REF TO zif_payment_service.lo_service = zcl_payment_factory=>create( 'PAYPAL' ).
" SCHLECHT: Konkrete Klasse verwendenDATA lo_paypal TYPE REF TO zcl_paypal_service.lo_paypal = NEW zcl_paypal_service( ).2. Factory-Methoden statt direkte Instanziierung
" GUT: Factory verwendenDATA(lo_document) = zcl_document_factory=>create( 'INVOICE' ).
" SCHLECHT: Direkte InstanziierungCASE lv_type. WHEN 'INVOICE'. lo_document = NEW zcl_invoice( ). WHEN 'DELIVERY'. lo_document = NEW zcl_delivery_note( ).ENDCASE.3. Exception fuer unbekannte Typen
METHOD create. CASE iv_type. WHEN 'A'. ro_result = NEW zcl_type_a( ). WHEN 'B'. ro_result = NEW zcl_type_b( ). WHEN OTHERS. " WICHTIG: Niemals NULL zurueckgeben RAISE EXCEPTION TYPE cx_invalid_type MESSAGE e001(zfactory) WITH iv_type. ENDCASE.ENDMETHOD.4. Lazy Initialization fuer teure Objekte
METHOD get_heavy_service. IF mo_heavy_service IS NOT BOUND. " Nur bei erster Anfrage erzeugen mo_heavy_service = NEW zcl_heavy_service( ). ENDIF. ro_service = mo_heavy_service.ENDMETHOD.5. Factory dokumentieren
"! Factory fuer Zahlungsabwickler"! <p>Erzeugt den passenden Payment-Processor basierend auf Zahlungsart.</p>"! @parameter iv_payment_method | CREDIT_CARD, PAYPAL, BANK_TRANSFER, INVOICE"! @parameter ro_processor | Der erzeugte Processor"! @raising cx_invalid_payment_method | Bei unbekannter ZahlungsartCLASS-METHODS create IMPORTING iv_payment_method TYPE string RETURNING VALUE(ro_processor) TYPE REF TO zif_payment_processor RAISING cx_invalid_payment_method.Haeufige Fehler vermeiden
1. Factory Inflation
Nicht fuer jede Klasse eine Factory erstellen. Nur wenn:
- Mehrere Implementierungen existieren
- Erzeugungslogik komplex ist
- Testbarkeit wichtig ist
2. Stateful Factories
" SCHLECHT: Factory mit ZustandCLASS zcl_bad_factory. DATA mv_counter TYPE i.
METHOD create. mv_counter = mv_counter + 1. " Problematisch bei paralleler Nutzung ENDMETHOD.ENDCLASS.
" GUT: Stateless FactoryCLASS zcl_good_factory. CLASS-METHODS create RETURNING VALUE(ro_result) TYPE REF TO zif_product.ENDCLASS.3. Zu viele Parameter
" SCHLECHT: Parameter-ExplosionMETHOD create IMPORTING iv_type iv_variant iv_region iv_customer iv_date. " Schwer wartbarENDMETHOD.
" GUT: Parameter-ObjektMETHOD create IMPORTING is_config TYPE zfactory_config.ENDMETHOD.Weiterführende Themen
- Design Patterns fuer RAP - Weitere Patterns wie Strategy und Facade
- Clean ABAP - Code-Qualitaet und Naming Conventions
- INTERFACE Statement - Interfaces in ABAP definieren
- Test Doubles und Mocking - Unit Tests mit Mock-Objekten
Fazit
Das Factory Pattern in seinen verschiedenen Varianten ist ein unverzichtbares Werkzeug fuer ABAP Cloud Entwicklung:
- Simple Factory fuer die meisten Anwendungsfaelle: Ein zentraler Ort fuer Objekterzeugung
- Factory Method fuer Vererbungshierarchien: Unterklassen bestimmen die konkrete Implementierung
- Abstract Factory fuer Produktfamilien: Zusammengehoerige Objekte konsistent erzeugen
- Factory + Dependency Injection fuer Testbarkeit: Mock-Objekte in Unit Tests injizieren
Der Schluessel liegt darin, das einfachste Pattern zu waehlen, das das Problem loest. Beginne mit Simple Factory und wechsle nur bei Bedarf zu komplexeren Varianten.