ABAP OOP Patterns Part 3: Strategy and Command Patterns
ABAP Design Patterns

ABAP OOP Patterns Part 3: Strategy and Command Patterns

If you’ve been following the design patterns conversation on this blog, you already know that applying OOP patterns in ABAP isn’t just academic exercise—it’s the difference between a system that survives business change and one that collapses under it. In Part 2, we covered Factory, Observer, and Decorator patterns, and in our deep dive into the Observer pattern, we saw how event-driven design transforms SAP workflows. Now it’s time to tackle three patterns that deal with a problem every SAP architect faces: how do you encapsulate behavior so that business logic stays clean, extensible, and testable?

Today we’re going deep on the Strategy, Command, and Template Method patterns in ABAP—with real-world SAP S/4HANA use cases that you can take straight into your next project.


Why These Three Patterns Matter in SAP Development

SAP systems are notorious for evolving business rules. Pricing logic changes quarterly. Approval workflows differ by region, company code, or document type. Output formatting depends on partner configuration. If you’re handling this with a cascade of IF/ELSEIF blocks or a single giant CASE statement, you already know the pain—every new requirement means touching fragile, shared code.

The Strategy, Command, and Template Method patterns each solve a slice of this problem. Let me show you how.


Pattern 1: Strategy — Swappable Business Logic Without Touching Core Code

The Problem It Solves

Imagine you’re building a pricing engine for a manufacturing client. Pricing rules differ by customer tier: standard, premium, and contract customers all follow different discount logic. The naive approach is a big CASE statement. The professional approach is the Strategy pattern.

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The consuming class doesn’t need to know which strategy it’s using—it just calls the interface.

ABAP Implementation

First, define the strategy interface:


""" Interface: Pricing Strategy """
INTERFACE zif_pricing_strategy.
  METHODS:
    calculate_discount
      IMPORTING
        iv_base_price   TYPE p DECIMALS 2
        iv_quantity     TYPE i
      RETURNING
        VALUE(rv_price) TYPE p DECIMALS 2.
ENDINTERFACE.

Now implement concrete strategies:


""" Concrete Strategy: Standard Customer Pricing """
CLASS zcl_standard_pricing DEFINITION PUBLIC FINAL.
  PUBLIC SECTION.
    INTERFACES zif_pricing_strategy.
ENDCLASS.

CLASS zcl_standard_pricing IMPLEMENTATION.
  METHOD zif_pricing_strategy~calculate_discount.
    " 5% discount for orders > 100 units
    IF iv_quantity > 100.
      rv_price = iv_base_price * '0.95'.
    ELSE.
      rv_price = iv_base_price.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

""" Concrete Strategy: Premium Customer Pricing """
CLASS zcl_premium_pricing DEFINITION PUBLIC FINAL.
  PUBLIC SECTION.
    INTERFACES zif_pricing_strategy.
ENDCLASS.

CLASS zcl_premium_pricing IMPLEMENTATION.
  METHOD zif_pricing_strategy~calculate_discount.
    " Flat 15% discount always
    rv_price = iv_base_price * '0.85'.
  ENDMETHOD.
ENDCLASS.

""" Concrete Strategy: Contract Customer Pricing """
CLASS zcl_contract_pricing DEFINITION PUBLIC FINAL.
  PUBLIC SECTION.
    INTERFACES zif_pricing_strategy.
ENDCLASS.

CLASS zcl_contract_pricing IMPLEMENTATION.
  METHOD zif_pricing_strategy~calculate_discount.
    " Tiered: 20% on first 500 units, 25% beyond
    DATA(lv_threshold) = CONV p( '500' ).
    IF iv_quantity <= lv_threshold.
      rv_price = iv_base_price * '0.80'.
    ELSE.
      rv_price = iv_base_price * '0.75'.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

The context class that consumes the strategy:


CLASS zcl_pricing_engine DEFINITION PUBLIC.
  PUBLIC SECTION.
    METHODS:
      constructor
        IMPORTING
          io_strategy TYPE REF TO zif_pricing_strategy,
      get_final_price
        IMPORTING
          iv_base_price   TYPE p DECIMALS 2
          iv_quantity     TYPE i
        RETURNING
          VALUE(rv_price) TYPE p DECIMALS 2.
  PRIVATE SECTION.
    DATA mo_strategy TYPE REF TO zif_pricing_strategy.
ENDCLASS.

CLASS zcl_pricing_engine IMPLEMENTATION.
  METHOD constructor.
    mo_strategy = io_strategy.
  ENDMETHOD.

  METHOD get_final_price.
    rv_price = mo_strategy->calculate_discount(
      iv_base_price = iv_base_price
      iv_quantity   = iv_quantity
    ).
  ENDMETHOD.
ENDCLASS.

Wiring It All Together


" Determine strategy based on customer classification
DATA(lo_strategy) = SWITCH REF #( iv_customer_class )
  WHEN 'PREMIUM'  THEN NEW zcl_premium_pricing()
  WHEN 'CONTRACT' THEN NEW zcl_contract_pricing()
  ELSE                 NEW zcl_standard_pricing( ).

DATA(lo_engine) = NEW zcl_pricing_engine( lo_strategy ).
DATA(lv_final_price) = lo_engine->get_final_price(
  iv_base_price = lv_unit_price
  iv_quantity   = lv_order_qty
).

The payoff: Adding a new customer tier requires zero changes to zcl_pricing_engine. You create a new strategy class, register it, and you’re done. This is the Open/Closed Principle in action.


Pattern 2: Command — Encapsulating Actions for Undo, Queuing, and Audit Trails

The Problem It Solves

The Command pattern is underused in SAP development, which is a shame because SAP systems are made for it. Think about approval workflows, document posting sequences, or any process where you need undo functionality, retry logic, or a full audit trail of what happened and when.

The Command pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations.

Real-World Use Case: Document Posting Workflow

Let’s say you have a workflow that posts a goods receipt, updates stock, and sends an IDoc notification. Each step is a command. If step 3 fails, you want to undo steps 1 and 2 in reverse order.


""" Command Interface """
INTERFACE zif_posting_command.
  METHODS:
    execute
      RAISING zcx_posting_error,
    undo
      RAISING zcx_posting_error.
ENDINTERFACE.

""" Concrete Command: Post Goods Receipt """
CLASS zcl_cmd_post_gr DEFINITION PUBLIC FINAL.
  PUBLIC SECTION.
    INTERFACES zif_posting_command.
    METHODS:
      constructor
        IMPORTING
          is_document TYPE zs_gr_document.
  PRIVATE SECTION.
    DATA ms_document   TYPE zs_gr_document.
    DATA mv_posted_doc TYPE mblnr.  " Material document number saved for undo
ENDCLASS.

CLASS zcl_cmd_post_gr IMPLEMENTATION.
  METHOD constructor.
    ms_document = is_document.
  ENDMETHOD.

  METHOD zif_posting_command~execute.
    " Call standard BAPI for goods receipt
    CALL FUNCTION 'BAPI_GOODSMVT_CREATE'
      EXPORTING
        goodsmvt_header = ms_document-header
      IMPORTING
        materialdocument = mv_posted_doc
      TABLES
        goodsmvt_item   = ms_document-items.

    " Check BAPI return for errors
    IF mv_posted_doc IS INITIAL.
      RAISE EXCEPTION TYPE zcx_posting_error
        EXPORTING
          mv_message = 'Goods receipt posting failed'.
    ENDIF.
  ENDMETHOD.

  METHOD zif_posting_command~undo.
    " Reverse the material document
    IF mv_posted_doc IS NOT INITIAL.
      CALL FUNCTION 'BAPI_GOODSMVT_CANCEL'
        EXPORTING
          materialdocument = mv_posted_doc.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

Now the Command Invoker that manages execution and rollback:


CLASS zcl_posting_invoker DEFINITION PUBLIC.
  PUBLIC SECTION.
    METHODS:
      add_command
        IMPORTING io_command TYPE REF TO zif_posting_command,
      execute_all
        RAISING zcx_posting_error,
      rollback_all
        RAISING zcx_posting_error.
  PRIVATE SECTION.
    DATA mt_commands        TYPE TABLE OF REF TO zif_posting_command.
    DATA mt_executed        TYPE TABLE OF REF TO zif_posting_command.
ENDCLASS.

CLASS zcl_posting_invoker IMPLEMENTATION.
  METHOD add_command.
    APPEND io_command TO mt_commands.
  ENDMETHOD.

  METHOD execute_all.
    LOOP AT mt_commands INTO DATA(lo_command).
      TRY.
          lo_command->execute( ).
          APPEND lo_command TO mt_executed.
        CATCH zcx_posting_error INTO DATA(lx_error).
          " Execution failed — trigger rollback
          rollback_all( ).
          RAISE EXCEPTION lx_error.
      ENDTRY.
    ENDLOOP.
  ENDMETHOD.

  METHOD rollback_all.
    " Undo in reverse order (LIFO)
    LOOP AT mt_executed INTO DATA(lo_command) REVERSE.
      lo_command->undo( ).
    ENDLOOP.
  ENDMETHOD.
ENDCLASS.

Why this is powerful: Each command is self-contained with its own undo logic. The invoker doesn’t care what the commands do—it just orchestrates them. This is exactly the kind of architecture that makes your code genuinely unit-testable—you can inject mock commands and verify the rollback sequence without touching a real SAP system.


Pattern 3: Template Method — Defining Process Skeletons with Flexible Steps

The Problem It Solves

The Template Method pattern is perfect for SAP output processing, report generation, or any workflow where the overall sequence of steps is fixed but the implementation of individual steps varies. Think: “always validate, always transform, always output—but the how of each step changes per document type.”

The Template Method defines the skeleton of an algorithm in a base class, deferring some steps to subclasses.

ABAP Implementation: Output Processing Framework


""" Abstract Base Class: Output Processor """
CLASS zcl_output_processor DEFINITION PUBLIC ABSTRACT.
  PUBLIC SECTION.
    "! Template method - defines the fixed sequence
    METHODS process_output
      IMPORTING is_context TYPE zs_output_context
      RAISING   zcx_output_error.
  PROTECTED SECTION.
    "! Steps to be implemented by subclasses
    METHODS:
      validate_data
        IMPORTING is_context TYPE zs_output_context
        RAISING   zcx_output_error ABSTRACT,
      transform_data
        IMPORTING     is_context     TYPE zs_output_context
        RETURNING     VALUE(rs_data) TYPE zs_output_data ABSTRACT,
      send_output
        IMPORTING is_data TYPE zs_output_data
        RAISING   zcx_output_error ABSTRACT,
      "! Hook method - optional override (not abstract)
      post_process
        IMPORTING is_data TYPE zs_output_data.
ENDCLASS.

CLASS zcl_output_processor IMPLEMENTATION.
  METHOD process_output.
    " This is the TEMPLATE METHOD - fixed algorithm skeleton
    validate_data( is_context ).

    DATA(ls_transformed) = transform_data( is_context ).

    send_output( ls_transformed ).

    " Hook: subclass may or may not override this
    post_process( ls_transformed ).
  ENDMETHOD.

  METHOD post_process.
    " Default: do nothing (hook method)
  ENDMETHOD.
ENDCLASS.

""" Concrete Implementation: PDF Invoice Output """
CLASS zcl_invoice_pdf_output DEFINITION PUBLIC
    INHERITING FROM zcl_output_processor FINAL.
  PROTECTED SECTION.
    METHODS:
      validate_data REDEFINITION,
      transform_data REDEFINITION,
      send_output REDEFINITION,
      post_process REDEFINITION.
ENDCLASS.

CLASS zcl_invoice_pdf_output IMPLEMENTATION.
  METHOD validate_data.
    " Invoice-specific validation: check partner, amount, tax
    IF is_context-partner_id IS INITIAL.
      RAISE EXCEPTION TYPE zcx_output_error
        EXPORTING mv_message = 'Partner ID missing for invoice output'.
    ENDIF.
  ENDMETHOD.

  METHOD transform_data.
    " Map SAP internal format to PDF rendering structure
    rs_data-document_type = 'INVOICE'.
    rs_data-content       = is_context-raw_data.
    rs_data-format        = 'PDF'.
  ENDMETHOD.

  METHOD send_output.
    " Call PDF generation and email dispatch
    zcl_pdf_mailer=>send(
      is_data      = is_data
      iv_recipient = zcl_partner_helper=>get_email( is_data-partner_id )
    ).
  ENDMETHOD.

  METHOD post_process.
    " Log successful invoice dispatch to custom table
    INSERT INTO zoutput_log VALUES @(
      VALUE #(
        mandt    = sy-mandt
        doc_type = 'INVOICE'
        sent_at  = sy-datum
        status   = 'S'
      )
    ).
  ENDMETHOD.
ENDCLASS.

The elegance here: When you add an EDI output type or an XML dispatch variant, you create a new subclass. The orchestration logic in process_output never changes. Every subclass is forced to implement the contract, and optional hooks give flexibility without forcing unnecessary overrides.

This pattern pairs beautifully with clean code refactoring strategies—when you’re untangling legacy SAP output programs, the Template Method is often the first architectural move you should make.


Combining Patterns: A Real-World Scenario

In practice, these patterns don’t live in isolation. Here’s a realistic combination I’ve deployed in a manufacturing SAP S/4HANA rollout:

  • Template Method defines the order processing sequence (validate → price → reserve → confirm)
  • Strategy handles the pricing step, swapping in the right algorithm per customer class
  • Command wraps each step so that if confirmation fails after stock reservation, we cleanly roll back the reservation

The result is a system that is open for extension, closed for modification, and—critically—safe to touch. Your junior developers can add new pricing strategies or output types without risk of breaking the core flow. That’s the real-world value of design patterns in SAP.


Common Pitfalls to Avoid

Over-Engineering Simple Cases

Don’t reach for Strategy if you only have two variants that will never change. A simple IF statement is cleaner. Apply patterns when you can clearly see the extension point.

Shallow Abstractions

A Command class with an execute method that does nothing testable is just noise. Make sure each command encapsulates real, meaningful behavior—and always implement undo if you’re managing state.

Ignoring ABAP Exception Handling

Every execute and undo method in your Command pattern should declare its exceptions explicitly. Combine this with the layered exception handling architecture we’ve covered previously to ensure errors propagate cleanly through the command chain.

Template Method with Too Many Abstract Methods

If every method in your template is abstract, you’ve essentially just created an interface. Keep the non-varying parts in the base class. The template should carry real logic, not just method signatures.


Testing These Patterns in ABAP Unit Tests

One of the biggest advantages of these three patterns is testability. Here’s a quick sketch for testing the Strategy pattern:


CLASS ltcl_pricing_strategy_test DEFINITION FOR TESTING RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    METHODS:
      test_premium_discount FOR TESTING,
      test_contract_tiered_pricing FOR TESTING.
ENDCLASS.

CLASS ltcl_pricing_strategy_test IMPLEMENTATION.
  METHOD test_premium_discount.
    DATA(lo_strategy) = NEW zcl_premium_pricing( ).
    DATA(lv_price) = lo_strategy->calculate_discount(
      iv_base_price = '100.00'
      iv_quantity   = 1
    ).
    cl_abap_unit_assert=>assert_equals(
      act = lv_price
      exp = '85.00'
      msg = 'Premium pricing should apply 15% discount'
    ).
  ENDMETHOD.

  METHOD test_contract_tiered_pricing.
    DATA(lo_strategy) = NEW zcl_contract_pricing( ).
    " Test above threshold
    DATA(lv_price) = lo_strategy->calculate_discount(
      iv_base_price = '100.00'
      iv_quantity   = 600
    ).
    cl_abap_unit_assert=>assert_equals(
      act = lv_price
      exp = '75.00'
      msg = 'Contract pricing above 500 units should apply 25% discount'
    ).
  ENDMETHOD.
ENDCLASS.

Clean, fast, no database dependencies. This is what reliable ABAP unit testing looks like in practice.


Key Takeaways

  • Strategy is your go-to for swappable business logic—pricing, tax calculation, approval routing
  • Command gives you undo, audit trails, and safe orchestration of multi-step SAP processes
  • Template Method enforces process consistency while allowing flexibility at specific steps
  • Combine patterns deliberately—they complement each other and scale well in complex SAP landscapes
  • Always pair pattern implementation with unit tests; the patterns are designed to make testing easy, so use that advantage

In the next part of this series, we’ll explore Composite and Chain of Responsibility patterns—both of which shine in hierarchical SAP data structures and approval workflow chains. If you’re dealing with organizational hierarchy processing or multi-level authorization logic, you’ll want to stay tuned.


What design patterns are you currently applying in your SAP projects? Are there specific business scenarios where you’re struggling to find the right pattern? Drop a comment below—I read every one and I’m happy to share what’s worked (and what hasn’t) from the field.