Live
Black Hat USADark ReadingBlack Hat AsiaAI BusinessCentOS Launches Accelerated Infrastructure Enablement For Driving NVIDIA AI Factories - PhoronixGNews AI NVIDIAAI #162: Visions of MythosLessWrong AIThe Fundrise Innovation Fund (VCX) Participates in OpenAI’s $122 Billion Funding Round - citybizGoogle News: OpenAIIBM, Arm team up to bring Arm software to IBM Z mainframesCIO MagazineAI project ‘failure’ has little to do with AI - ComputerworldGoogle News: Generative AIAnaxi Labs Partners with Carnegie Mellon to Tackle AI's Biggest Problem: Economics - Lexington Herald LeaderGoogle News: Generative AIOpenAI’s record $122 billion round is just the start - The Business JournalsGoogle News: OpenAIPrediction: Nvidia Will Do the Unthinkable and Hit $100 Before the End of 2026 - The Motley FoolGNews AI NVIDIAAmii Launches Technical Track for Software Pros as Part of ‘AI Pathways’ Program - Calgary.TechGoogle News: Machine LearningI wrote a novel using AI. Writers must accept artificial intelligence – but we are as valuable as ever - The GuardianGoogle News: AIWill AI make it harder for non-graduates to climb the jobs ladder?Financial Times TechColumn: For the Children – Artificial Intelligence brings new risks for our children - Duncan BannerGoogle News: AIBlack Hat USADark ReadingBlack Hat AsiaAI BusinessCentOS Launches Accelerated Infrastructure Enablement For Driving NVIDIA AI Factories - PhoronixGNews AI NVIDIAAI #162: Visions of MythosLessWrong AIThe Fundrise Innovation Fund (VCX) Participates in OpenAI’s $122 Billion Funding Round - citybizGoogle News: OpenAIIBM, Arm team up to bring Arm software to IBM Z mainframesCIO MagazineAI project ‘failure’ has little to do with AI - ComputerworldGoogle News: Generative AIAnaxi Labs Partners with Carnegie Mellon to Tackle AI's Biggest Problem: Economics - Lexington Herald LeaderGoogle News: Generative AIOpenAI’s record $122 billion round is just the start - The Business JournalsGoogle News: OpenAIPrediction: Nvidia Will Do the Unthinkable and Hit $100 Before the End of 2026 - The Motley FoolGNews AI NVIDIAAmii Launches Technical Track for Software Pros as Part of ‘AI Pathways’ Program - Calgary.TechGoogle News: Machine LearningI wrote a novel using AI. Writers must accept artificial intelligence – but we are as valuable as ever - The GuardianGoogle News: AIWill AI make it harder for non-graduates to climb the jobs ladder?Financial Times TechColumn: For the Children – Artificial Intelligence brings new risks for our children - Duncan BannerGoogle News: AI
AI NEWS HUBbyEIGENVECTOREigenvector

ABAP OOP Design Patterns — Part 2: Factory, Observer, and Decorator Patterns in Real SAP Systems

DEV Communityby Oktay AtesApril 1, 202611 min read1 views
Source Quiz

<p>ABAP OOP Design Patterns — Part 2: Factory, Observer, and Decorator Patterns in Real SAP Systems<br> If you’ve been writing ABAP long enough, you’ve probably inherited a codebase where every business rule is buried inside a 3,000-line function module, and adding a new requirement means copy-pasting logic you’re not even sure is correct. That’s not a skills problem — it’s an architecture problem. In <a href="https://aixsap.com/abap-oop-ile-tasarim-desenleri-abap-oop-ve-strategy-pattern-gercek-dunya-uygulamalari/" rel="noopener noreferrer">Part 1 of this series</a>, we explored how the Strategy Pattern helps you swap business logic cleanly without touching the calling code. Now it’s time to go further. In this second installment, we’re tackling three more battle-tested <strong>ABAP OOP de

ABAP OOP Design Patterns — Part 2: Factory, Observer, and Decorator Patterns in Real SAP Systems If you’ve been writing ABAP long enough, you’ve probably inherited a codebase where every business rule is buried inside a 3,000-line function module, and adding a new requirement means copy-pasting logic you’re not even sure is correct. That’s not a skills problem — it’s an architecture problem. In Part 1 of this series, we explored how the Strategy Pattern helps you swap business logic cleanly without touching the calling code. Now it’s time to go further. In this second installment, we’re tackling three more battle-tested ABAP OOP design patterns: Factory, Observer, and Decorator. Each one solves a very specific pain point I’ve encountered repeatedly across large SAP S/4HANA implementations — and I’ll show you exactly how to apply them.

Before we dive in, a quick note: these patterns aren’t academic exercises. Every example below is inspired by real implementation challenges on production SAP systems. If you’re also working on improving code quality across the board, it’s worth reading alongside our guides on Clean ABAP best practices and robust exception handling in ABAP.

Why These Three Patterns Matter in SAP Environments

SAP systems are notorious for their complexity — multiple integration touchpoints, constantly changing business rules, and the eternal challenge of extending standard functionality without breaking things. Design patterns give you a shared vocabulary and proven blueprints for solving these recurring structural problems.

Here’s a quick orientation before we get into code:

  • Factory Pattern: Controls how objects are created, keeping instantiation logic away from business logic.

  • Observer Pattern: Decouples event producers from event consumers — critical in event-driven SAP architectures.

  • Decorator Pattern: Adds behavior to objects dynamically without changing the original class — your best friend when extending standard SAP logic.

Let’s get into each one.

Pattern 1: The Factory Pattern — Stop Hardcoding Object Creation

The Problem It Solves

Imagine you have a pricing engine that needs to instantiate different pricing strategies based on customer type: standard, VIP, or wholesale. Without a factory, you’ll see code like this scattered everywhere:

" The anti-pattern: instantiation logic bleeding into business logic IF lv_customer_type = 'VIP'.  CREATE OBJECT lo_pricing TYPE zcl_vip_pricing. ELSEIF lv_customer_type = 'WHOLESALE'.  CREATE OBJECT lo_pricing TYPE zcl_wholesale_pricing. ELSE.  CREATE OBJECT lo_pricing TYPE zcl_standard_pricing. ENDIF.

Enter fullscreen mode

Exit fullscreen mode

Now imagine this block duplicated across 12 different programs. The day you add a new customer type, you’re hunting through the entire codebase. That’s exactly the problem the Factory Pattern eliminates.

Implementing a Simple Factory in ABAP

First, define your interface:

INTERFACE zif_pricing_strategy.  METHODS:  calculate_price  IMPORTING iv_base_price TYPE p DECIMALS 2  iv_quantity TYPE i  RETURNING VALUE(rv_final_price) TYPE p DECIMALS 2. ENDINTERFACE.

Enter fullscreen mode

Exit fullscreen mode

Then implement your concrete classes:

" Standard pricing: no discount CLASS zcl_standard_pricing DEFINITION PUBLIC FINAL.  PUBLIC SECTION.  INTERFACES zif_pricing_strategy. ENDCLASS.

CLASS zcl_standard_pricing IMPLEMENTATION. METHOD zif_pricing_strategy~calculate_price. rv_final_price = iv_base_price * iv_quantity. ENDMETHOD. ENDCLASS.*

" VIP pricing: 15% discount CLASS zcl_vip_pricing DEFINITION PUBLIC FINAL. PUBLIC SECTION. INTERFACES zif_pricing_strategy. ENDCLASS.

CLASS zcl_vip_pricing IMPLEMENTATION. METHOD zif_pricing_strategy~calculate_price. rv_final_price = iv_base_price * iv_quantity * '0.85'. ENDMETHOD. ENDCLASS.`

Enter fullscreen mode

Exit fullscreen mode

Now, the factory class itself — this is where the magic lives:

CLASS zcl_pricing_factory DEFINITION PUBLIC FINAL CREATE PRIVATE.  PUBLIC SECTION.  CLASS-METHODS:  get_instance  RETURNING VALUE(ro_factory) TYPE REF TO zcl_pricing_factory,  create_strategy  IMPORTING iv_customer_type TYPE char10  RETURNING VALUE(ro_strategy) TYPE REF TO zif_pricing_strategy  RAISING zcx_unknown_customer_type.

PRIVATE SECTION. CLASS-DATA: go_instance TYPE REF TO zcl_pricing_factory. ENDCLASS.

CLASS zcl_pricing_factory IMPLEMENTATION.

METHOD get_instance. " Singleton: only one factory instance needed IF go_instance IS NOT BOUND. CREATE OBJECT go_instance. ENDIF. ro_factory = go_instance. ENDMETHOD.

METHOD create_strategy. CASE iv_customer_type. WHEN 'STANDARD'. CREATE OBJECT ro_strategy TYPE zcl_standard_pricing. WHEN 'VIP'. CREATE OBJECT ro_strategy TYPE zcl_vip_pricing. WHEN 'WHOLESALE'. CREATE OBJECT ro_strategy TYPE zcl_wholesale_pricing. WHEN OTHERS. " Always raise a typed exception — see our exception handling guide RAISE EXCEPTION TYPE zcx_unknown_customer_type EXPORTING iv_customer_type = iv_customer_type. ENDCASE. ENDMETHOD.

ENDCLASS.`

Enter fullscreen mode

Exit fullscreen mode

Now your business code becomes clean and future-proof:

DATA: lo_factory TYPE REF TO zcl_pricing_factory,  lo_strategy TYPE REF TO zif_pricing_strategy.

lo_factory = zcl_pricing_factory=>get_instance( ).

TRY. lo_strategy = lo_factory->create_strategy( iv_customer_type = lv_cust_type ). DATA(lv_price) = lo_strategy->calculate_price( iv_base_price = '100.00' iv_quantity = 5 ). CATCH zcx_unknown_customer_type INTO DATA(lx_exc). " Log and handle gracefully ENDTRY.`

Enter fullscreen mode

Exit fullscreen mode

Adding a new customer type now means adding one class and one WHEN clause in the factory. Nothing else changes. That’s the power of encapsulated object creation.

Pattern 2: The Observer Pattern — Event-Driven Logic Without Tight Coupling

The Problem It Solves

Consider a sales order creation process. When an order is created, you might need to: send a notification email, update a reporting table, and trigger a downstream MES workflow. Without the Observer pattern, your order creation class ends up calling all of these directly — a violation of the Single Responsibility Principle and a maintenance nightmare.

The Observer pattern lets you define a subject (the order) and observers (notification, reporting, MES integration) that register themselves and react independently.

ABAP Implementation

" Observer interface — all listeners must implement this INTERFACE zif_order_observer.  METHODS:  on_order_created  IMPORTING is_order_data TYPE zs_sales_order. ENDINTERFACE.

" Subject interface — the observable entity INTERFACE zif_order_subject. METHODS: attach IMPORTING io_observer TYPE REF TO zif_order_observer, detach IMPORTING io_observer TYPE REF TO zif_order_observer, notify IMPORTING is_order_data TYPE zs_sales_order. ENDINTERFACE.`

Enter fullscreen mode

Exit fullscreen mode

" Concrete subject: Sales Order processor CLASS zcl_sales_order_processor DEFINITION PUBLIC.  PUBLIC SECTION.  INTERFACES zif_order_subject.  METHODS create_order  IMPORTING is_order_data TYPE zs_sales_order.  PRIVATE SECTION.  DATA: gt_observers TYPE TABLE OF REF TO zif_order_observer. ENDCLASS.

CLASS zcl_sales_order_processor IMPLEMENTATION.

METHOD zif_order_subject~attach. APPEND io_observer TO gt_observers. ENDMETHOD.

METHOD zif_order_subject~detach. DELETE gt_observers WHERE table_line = io_observer. ENDMETHOD.

METHOD zif_order_subject~notify. LOOP AT gt_observers INTO DATA(lo_observer). lo_observer->on_order_created( is_order_data = is_order_data ). ENDLOOP. ENDMETHOD.

METHOD create_order. " Core order creation logic here... " (database insert, number range, etc.)

" Notify all registered observers zif_order_subject~notify( is_order_data = is_order_data ). ENDMETHOD.

ENDCLASS.`

Enter fullscreen mode

Exit fullscreen mode

" Concrete Observer 1: Email notification CLASS zcl_order_email_notifier DEFINITION PUBLIC FINAL.  PUBLIC SECTION.  INTERFACES zif_order_observer. ENDCLASS.

CLASS zcl_order_email_notifier IMPLEMENTATION. METHOD zif_order_observer~on_order_created. " Send confirmation email logic " cl_bcs or custom email class here WRITE: / 'Email sent for order:', is_order_data-order_id. ENDMETHOD. ENDCLASS.

" Concrete Observer 2: MES trigger CLASS zcl_order_mes_trigger DEFINITION PUBLIC FINAL. PUBLIC SECTION. INTERFACES zif_order_observer. ENDCLASS.

CLASS zcl_order_mes_trigger IMPLEMENTATION. METHOD zif_order_observer~on_order_created. " Trigger MES workflow via REST call or IDoc " See: SAP MES Integration architecture article WRITE: / 'MES workflow triggered for order:', is_order_data-order_id. ENDMETHOD. ENDCLASS.`

Enter fullscreen mode

Exit fullscreen mode

Wiring it all together:

DATA: lo_processor TYPE REF TO zcl_sales_order_processor,  lo_email TYPE REF TO zcl_order_email_notifier,  lo_mes TYPE REF TO zcl_order_mes_trigger.

CREATE OBJECT lo_processor. CREATE OBJECT lo_email. CREATE OBJECT lo_mes.

" Register observers lo_processor->attach( lo_email ). lo_processor->attach( lo_mes ).

" Create order — observers fire automatically lo_processor->create_order( is_order_data = ls_order ).`

Enter fullscreen mode

Exit fullscreen mode

Want to add a reporting observer next month? Create the class, register it. The processor never changes. This is exactly the kind of extensibility you need in a living SAP system — and it pairs beautifully with the event-driven approach we discussed in the context of SAP BTP Event Mesh architectures.

Pattern 3: The Decorator Pattern — Extending Behavior Without Inheritance Hell

The Problem It Solves

Here’s a scenario I see regularly: you have a report output class that formats data. Now stakeholders want optional features — logging, caching, and access control — applied in different combinations. Using inheritance, you’d need a class for every combination: LoggingCachingOutputFormatter, CachingOutputFormatter, etc. That explodes fast.

The Decorator pattern wraps objects to add behavior dynamically, without subclassing. It’s composable, testable, and elegant.

ABAP Implementation

" Core interface INTERFACE zif_data_formatter.  METHODS:  format_data  IMPORTING it_raw_data TYPE ztt_report_data  RETURNING VALUE(rv_output) TYPE string. ENDINTERFACE.

" Base concrete implementation CLASS zcl_base_formatter DEFINITION PUBLIC. PUBLIC SECTION. INTERFACES zif_data_formatter. ENDCLASS.

CLASS zcl_base_formatter IMPLEMENTATION. METHOD zif_data_formatter~format_data. " Core formatting logic — converts internal table to string output LOOP AT it_raw_data INTO DATA(ls_row). CONCATENATE rv_output ls_row-field1 '|' ls_row-field2 CL_ABAP_CHAR_UTILITIES=>NEWLINE INTO rv_output. ENDLOOP. ENDMETHOD. ENDCLASS.`

Enter fullscreen mode

Exit fullscreen mode

" Abstract decorator base — holds a reference to the wrapped component CLASS zcl_formatter_decorator DEFINITION PUBLIC ABSTRACT.  PUBLIC SECTION.  INTERFACES zif_data_formatter.  METHODS constructor  IMPORTING io_wrapped TYPE REF TO zif_data_formatter.  PROTECTED SECTION.  DATA: mo_wrapped TYPE REF TO zif_data_formatter. ENDCLASS.

CLASS zcl_formatter_decorator IMPLEMENTATION. METHOD constructor. mo_wrapped = io_wrapped. ENDMETHOD. METHOD zif_data_formatter~format_data. " Default: delegate to the wrapped component rv_output = mo_wrapped->format_data( it_raw_data = it_raw_data ). ENDMETHOD. ENDCLASS.`

Enter fullscreen mode

Exit fullscreen mode

" Logging decorator: wraps any formatter and adds execution logging CLASS zcl_logging_formatter DEFINITION PUBLIC INHERITING FROM zcl_formatter_decorator FINAL.  PUBLIC SECTION.  METHODS zif_data_formatter~format_data REDEFINITION. ENDCLASS.

CLASS zcl_logging_formatter IMPLEMENTATION. METHOD zif_data_formatter~format_data. DATA(lv_start) = cl_abap_systime=>get_current_time( ).

" Delegate to wrapped formatter rv_output = mo_wrapped->format_data( it_raw_data = it_raw_data ).

DATA(lv_end) = cl_abap_systime=>get_current_time( ). " Log execution time to application log MESSAGE |Formatter executed in { lv_end - lv_start } ms| TYPE 'I'. ENDMETHOD. ENDCLASS.

" Caching decorator: returns cached output if data hasn't changed CLASS zcl_caching_formatter DEFINITION PUBLIC INHERITING FROM zcl_formatter_decorator FINAL. PUBLIC SECTION. METHODS zif_data_formatter~format_data REDEFINITION. PRIVATE SECTION. DATA: mv_cache TYPE string, mv_cache_key TYPE string. ENDCLASS.

CLASS zcl_caching_formatter IMPLEMENTATION. METHOD zif_data_formatter~format_data. " Simple hash-based cache key from row count + first key field DATA(lv_key) = |{ lines( it_raw_data ) }|.

IF lv_key = mv_cache_key AND mv_cache IS NOT INITIAL. rv_output = mv_cache. " Return cached result RETURN. ENDIF.

rv_output = mo_wrapped->format_data( it_raw_data = it_raw_data ). mv_cache = rv_output. mv_cache_key = lv_key. ENDMETHOD. ENDCLASS.`

Enter fullscreen mode

Exit fullscreen mode

Now compose them however you need, at runtime:

" Build a formatter stack: Base → Cache → Log DATA: lo_base TYPE REF TO zif_data_formatter,  lo_cached TYPE REF TO zif_data_formatter,  lo_logged TYPE REF TO zif_data_formatter.

CREATE OBJECT lo_base TYPE zcl_base_formatter. CREATE OBJECT lo_cached TYPE zcl_caching_formatter EXPORTING io_wrapped = lo_base. CREATE OBJECT lo_logged TYPE zcl_logging_formatter EXPORTING io_wrapped = lo_cached.

" The caller uses the interface — unaware of what's underneath DATA(lv_output) = lo_logged->format_data( it_raw_data = lt_data ).`

Enter fullscreen mode

Exit fullscreen mode

Swap decorators in and out based on configuration. Add an access-control decorator for certain users. None of the underlying classes change. This is composition over inheritance in action — one of the most important principles in the Clean ABAP guidelines.

When to Use Which Pattern — A Decision Guide

Pattern Use When Avoid When

Factory Object creation logic is complex or type-dependent You only ever create one type of object

Observer One event triggers multiple independent reactions Observers are tightly ordered and dependent on each other

Decorator You need to combine behaviors at runtime without subclassing Behavior combinations are fixed and few

Making Patterns Testable with ABAP Unit

One underrated benefit of these patterns is testability. Because each component depends on interfaces, not concrete classes, you can inject test doubles easily. If you’re not yet writing unit tests for your ABAP classes, now is the time — our dedicated guide on ABAP unit testing in SAP S/4HANA walks through exactly how to structure tests for OOP-based code.

For example, testing the Observer pattern is trivial with a mock observer:

CLASS zcl_mock_observer DEFINITION FOR TESTING.  PUBLIC SECTION.  INTERFACES zif_order_observer.  DATA: mv_was_called TYPE abap_bool,  ms_received TYPE zs_sales_order. ENDCLASS.

CLASS zcl_mock_observer IMPLEMENTATION. METHOD zif_order_observer~on_order_created. mv_was_called = abap_true. ms_received = is_order_data. ENDMETHOD. ENDCLASS.`

Enter fullscreen mode

Exit fullscreen mode

Inject the mock, trigger the action, assert mv_was_called = abap_true. Clean, isolated, fast.

Key Takeaways

  • The Factory Pattern centralizes object creation and makes your codebase extensible without scattering CREATE OBJECT logic everywhere.

  • The Observer Pattern decouples event producers from consumers, making it easy to add new reactions to system events without modifying existing code.

  • The Decorator Pattern lets you compose behaviors dynamically — far more flexible than deep inheritance hierarchies.

  • All three patterns make your code significantly easier to unit test, a non-negotiable requirement in modern SAP development.

  • These aren’t theoretical niceties — they solve concrete problems you face in every mid-to-large SAP project.

If you’re just getting started with OOP in ABAP and found this article dense, I’d recommend revisiting the Strategy Pattern from Part 1 of this series first — it lays the foundation that makes these patterns click.

In Part 3, we’ll explore the Command and Template Method patterns — both essential for building undo/redo mechanisms, batch processing pipelines, and workflow engines in SAP. Stay tuned.

What’s your experience with design patterns in ABAP? Have you applied any of these on a real project? I’d love to hear what worked, what didn’t, and what challenges you ran into. Drop a comment below or connect with me — let’s keep the conversation going.

Was this article helpful?

Sign in to highlight and annotate this article

AI
Ask AI about this article
Powered by Eigenvector · full article context loaded
Ready

Conversation starters

Ask anything about this article…

Daily AI Digest

Get the top 5 AI stories delivered to your inbox every morning.

More about

updateproductapplication

Knowledge Map

Knowledge Map
TopicsEntitiesSource
ABAP OOP De…updateproductapplicationfeatureintegrationreportDEV Communi…

Connected Articles — Knowledge Graph

This article is connected to other articles through shared AI topics and tags.

Knowledge Graph100 articles · 181 connections
Scroll to zoom · drag to pan · click to open

Discussion

Sign in to join the discussion

No comments yet — be the first to share your thoughts!

More in Products