Factory Pattern in ABAP Cloud: Flexible Objekterzeugung

kategorie
Best Practices
Veröffentlicht
autor
Johannes

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?

ProblemLoesung durch Factory
Hartkodierte KlasseninstanziierungZentrale Erzeugungslogik
Schwer testbarer CodeMock-Objekte einfach injizierbar
Keine Flexibilitaet bei VariantenwechselLaufzeit-Entscheidung moeglich
Duplizierte ErzeugungslogikSingle Point of Creation
Enge Kopplung an konkrete KlassenProgrammieren gegen Interfaces

Die drei Factory-Varianten

Es gibt drei Hauptvarianten des Factory Patterns, die sich in Komplexitaet und Anwendungsfall unterscheiden:

VarianteKomplexitaetAnwendungsfall
Simple FactoryNiedrigEine Produktfamilie, statische Erzeugung
Factory MethodMittelVererbungshierarchie, Subklassen bestimmen Typ
Abstract FactoryHochMehrere 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 Zahlungsabwicklung
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.
" Implementierung: Kreditkarte
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.
" 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: 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.
" 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: Bankueberweisung
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.
" 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 Method
CLASS 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 Rechnungen
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.
" 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 Lieferscheine
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.
" 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 Auftragsbestaetigungen
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.
" 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-Auswahl
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.

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 Produkte
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.

Abstract Factory Interface

" Abstract Factory Interface
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.

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 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.
" ===== 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 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 fuer 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.

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.
" Verwendung
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 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 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 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-Implementierung
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 - nichts tun
ENDMETHOD.
ENDCLASS.

Wann welches Pattern verwenden?

SituationEmpfohlenes PatternBegruendung
Erzeugung basierend auf ParameterSimple FactoryEinfach, zentral, leicht verstaendlich
Vererbungshierarchie mit ProduktenFactory MethodUnterklassen bestimmen konkrete Typen
Zusammengehoerige ProduktfamilienAbstract FactoryKonsistenz zwischen verwandten Objekten
Testbarkeit ist kritischSimple Factory + DIMock-Injection zur Testzeit
Nur wenige VariantenSimple FactoryOverhead gering halten
Viele Varianten mit GemeinsamkeitenFactory MethodVererbung nutzen

Best Practices

1. Interface-First Design

" GUT: Immer gegen Interfaces programmieren
DATA lo_service TYPE REF TO zif_payment_service.
lo_service = zcl_payment_factory=>create( 'PAYPAL' ).
" SCHLECHT: Konkrete Klasse verwenden
DATA lo_paypal TYPE REF TO zcl_paypal_service.
lo_paypal = NEW zcl_paypal_service( ).

2. Factory-Methoden statt direkte Instanziierung

" GUT: Factory verwenden
DATA(lo_document) = zcl_document_factory=>create( 'INVOICE' ).
" SCHLECHT: Direkte Instanziierung
CASE 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 Zahlungsart
CLASS-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 Zustand
CLASS zcl_bad_factory.
DATA mv_counter TYPE i.
METHOD create.
mv_counter = mv_counter + 1.
" Problematisch bei paralleler Nutzung
ENDMETHOD.
ENDCLASS.
" GUT: Stateless Factory
CLASS zcl_good_factory.
CLASS-METHODS create
RETURNING VALUE(ro_result) TYPE REF TO zif_product.
ENDCLASS.

3. Zu viele Parameter

" SCHLECHT: Parameter-Explosion
METHOD create
IMPORTING
iv_type iv_variant iv_region iv_customer iv_date.
" Schwer wartbar
ENDMETHOD.
" GUT: Parameter-Objekt
METHOD create
IMPORTING
is_config TYPE zfactory_config.
ENDMETHOD.

Weiterführende Themen

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.