Object-Oriented ABAP Masterclass: Classes, Interfaces, Inheritance, Design Patterns & ABAP Unit – Hands-On Tutorial | Part 46 | FreeLearning365

 

Object-Oriented ABAP Masterclass: Classes, Interfaces, Inheritance, Design Patterns & ABAP Unit – Hands-On Tutorial | Part 46 | FreeLearning365


Object-Oriented ABAP – Classes, Interfaces, Inheritance & Design Patterns: A 25,000+ Word Hands-On Masterclass

Day 6 of the S/4HANA ABAP Development & Fiori Track

By @FreeLearning365 and Tech Partner @techbook24

Introduction: The 5000-Line Include That Broke the Team

It’s code review day at TechBook24, and a senior consultant has just opened the legacy procurement reporting tool. It’s a single ABAP include, roughly 5,200 lines long, containing everything from data selection to ALV display to PDF generation. There are global variables scattered everywhere, no functions, and a spiderweb of IF-THEN-ELSE chains that no one dares to touch. Adding a new requirement — say, a different layout for subcontract POs — would mean risking the entire report. The team has been putting off changes for months.

Arjun is tasked with rewriting this into a modular, maintainable architecture that can be extended without fear. He decides to apply object-oriented ABAP. This tutorial follows his journey from class design to testing. By the end, you’ll have built a complete OO procurement dashboard system, with clean separation of concerns, a factory pattern for dynamic processor selection, interfaces that define contracts, and ABAP Unit tests that guarantee your logic works. You’ll also understand why SAP’s own code is moving entirely towards OO — and why your new developments should too.

If you’ve ever thought OO ABAP is “too complex” or “not needed for reports,” this deep dive will change your mind. We’ll go step-by-step, with real SE24 screens, actual UML diagrams described, and every code snippet ready to adapt. Let’s build software, not just programs.

1. The Core Concepts – Classes, Objects, and Encapsulation

1.1 What Is a Class? The Blueprint

A class is a template that defines the attributes (data) and methods (behavior) of objects. In ABAP, you create a class using transaction SE24 (Class Builder) or directly in ADT. For Arjun’s dashboard, he needs a class to represent a Purchase Order. Rather than handling raw EKKO/EKPO data everywhere, he’ll create ZCL_PO that encapsulates all PO-related data and operations.

Creating ZCL_PO in SE24:

  1. SE24 → Object Type → Class → enter ZCL_PO → Create.
  2. Set Description: "Purchase Order Object".
  3. Select Instantiation: Public (so can be created from anywhere).
  4. Define attributes: in the "Attributes" tab, he creates private attributes to hold the header and items data. He chooses private to enforce encapsulation — external code cannot directly access these; they must use public getter methods.
    • MV_EBELN TYPE EBELN (public read-only? He'll provide a getter, but he makes it private).
    • MS_HEADER TYPE EKKO (private).
    • MT_ITEMS TYPE TT_EKPO (private).
  5. Define methods:
    • CONSTRUCTOR – importing IV_EBELN, to load data.
    • GET_HEADER – returning RS_HEADER.
    • GET_ITEMS – returning RT_ITEMS.
    • GET_TOTAL_VALUE – returning RV_TOTAL.

Screenshot Description: SE24 Class Builder with ZCL_PO attributes and methods list.

Encapsulation means that if later the data source changes (e.g., to a CDS view), only the internal implementation of ZCL_PO changes; the external callers remain unaffected.

1.2 Implementing the Constructor and Methods

CLASS zcl_po DEFINITION.
  PUBLIC SECTION.
    METHODS: constructor IMPORTING iv_ebeln TYPE ebeln,
             get_header RETURNING VALUE(rs_header) TYPE ekko,
             get_items RETURNING VALUE(rt_items) TYPE tt_ekpo,
             get_total_value RETURNING VALUE(rv_total) TYPE ekpo-netwr.
  PRIVATE SECTION.
    DATA: mv_ebeln TYPE ebeln,
          ms_header TYPE ekko,
          mt_items  TYPE tt_ekpo.
ENDCLASS.

CLASS zcl_po IMPLEMENTATION.
  METHOD constructor.
    mv_ebeln = iv_ebeln.
    SELECT SINGLE * FROM ekko WHERE ebeln = @mv_ebeln INTO @ms_header.
    IF sy-subrc <> 0.
      " raise exception (we'll handle later)
      RAISE EXCEPTION TYPE cx_sy_no_entry.
    ENDIF.
    SELECT * FROM ekpo WHERE ebeln = @mv_ebeln INTO TABLE @mt_items.
  ENDMETHOD.

  METHOD get_header.
    rs_header = ms_header.
  ENDMETHOD.

  METHOD get_items.
    rt_items = mt_items.
  ENDMETHOD.

  METHOD get_total_value.
    LOOP AT mt_items INTO DATA(ls_item).
      rv_total = rv_total + ls_item-netwr.
    ENDLOOP.
  ENDMETHOD.
ENDCLASS.

Now any program can do:

DATA(lo_po) = NEW zcl_po( iv_ebeln = '4500000123' ).
DATA(lv_total) = lo_po->get_total_value( ).

This is cleaner and reusable. Arjun creates similar classes for Vendor (ZCL_VENDOR) and Material (ZCL_MATERIAL).

2. Inheritance – Building a Family of PO Processors

2.1 The Need for Specialization

Not all purchase orders are the same. TechBook24 deals with standard POs, subcontract POs, and stock transport orders. Each has slightly different validation rules and additional data. Instead of cluttering ZCL_PO with endless IFs, Arjun uses inheritance. He defines a base class ZCL_PO_BASE with common functionality, and then subclasses ZCL_PO_STANDARD, ZCL_PO_SUBCON, ZCL_PO_STOCK_TRANS that override or extend specific methods.

2.2 Creating the Base Class

ZCL_PO_BASE is similar to ZCL_PO but abstract (cannot be instantiated directly). It defines the common structure, and a method VALIDATE that must be implemented in subclasses (abstract).

CLASS zcl_po_base DEFINITION ABSTRACT.
  PUBLIC SECTION.
    METHODS: constructor IMPORTING iv_ebeln TYPE ebeln,
             get_header RETURNING VALUE(rs_header) TYPE ekko,
             get_items RETURNING VALUE(rt_items) TYPE tt_ekpo,
             validate ABSTRACT RETURNING VALUE(rv_valid) TYPE abap_bool.
  PROTECTED SECTION.
    DATA: ms_header TYPE ekko,
          mt_items  TYPE tt_ekpo.
ENDCLASS.

Then ZCL_PO_STANDARD inherits from it:

CLASS zcl_po_standard DEFINITION INHERITING FROM zcl_po_base.
  PUBLIC SECTION.
    METHODS: validate REDEFINITION.
ENDCLASS.

CLASS zcl_po_standard IMPLEMENTATION.
  METHOD validate.
    " Standard PO specific validation: e.g., check material master exists
    rv_valid = abap_true.
    " ... actual checks
  ENDMETHOD.
ENDCLASS.

Similarly, ZCL_PO_SUBCON adds a method GET_COMPONENTS that returns components for subcontracting, while still inheriting the base. This demonstrates polymorphism — the caller can treat any subclass as ZCL_PO_BASE and call VALIDATE, without knowing the exact type.

3. Interfaces – Defining Contracts for Pluggable Logic

3.1 Why Interfaces Matter

Interfaces define a set of methods that any class can implement, without being part of the same inheritance tree. SAP heavily uses interfaces: IF_SERIALIZABLE_OBJECT, IF_BADI_INTERFACE, etc. Arjun wants his dashboard to use different data sources (database, CDS, or even mock data for testing). He defines an interface ZIF_PO_DATA_PROVIDER with a method GET_OPEN_POS returning a list of PO headers. Any class implementing this interface can be used interchangeably.

3.2 Creating and Implementing an Interface

SE24: Create interface ZIF_PO_DATA_PROVIDER. Add method:

GET_OPEN_POS IMPORTING IV_DATE_FROM TYPE datum
              EXPORTING ET_HEADERS TYPE tt_ekko.

Then, a real database provider ZCL_DB_PO_PROVIDER implements it:

CLASS zcl_db_po_provider DEFINITION.
  PUBLIC SECTION.
    INTERFACES: zif_po_data_provider.
ENDCLASS.

CLASS zcl_db_po_provider IMPLEMENTATION.
  METHOD zif_po_data_provider~get_open_pos.
    SELECT * FROM ekko WHERE aedat >= iv_date_from AND loekz = space
      INTO TABLE et_headers.
  ENDMETHOD.
ENDCLASS.

A test mock provider can return a fixed set of data for unit testing, enabling fast, repeatable tests without database access. Arjun will use this extensively.

4. The Factory Pattern – Creating Objects Without New

4.1 The Problem with Hard-Coded Types

If Arjun uses NEW zcl_po_standard( ) directly in code, he ties the calling program to a specific subclass. Adding a new PO type requires finding and changing every instantiation. A factory pattern centralizes object creation.

He creates a factory class ZCL_PO_FACTORY with a static method CREATE_PO that takes the PO number, reads the header to determine the document type (BSART), and returns the appropriate subclass reference.

4.2 Implementation of the Factory

CLASS zcl_po_factory DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS create_po IMPORTING iv_ebeln TYPE ebeln
                            RETURNING VALUE(ro_po) TYPE REF TO zcl_po_base
                            RAISING   cx_sy_no_entry.
ENDCLASS.

CLASS zcl_po_factory IMPLEMENTATION.
  METHOD create_po.
    SELECT SINGLE bsart FROM ekko WHERE ebeln = @iv_ebeln INTO @DATA(lv_bsart).
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE cx_sy_no_entry.
    ENDIF.
    CASE lv_bsart.
      WHEN 'NB'. " Standard PO
        ro_po = NEW zcl_po_standard( iv_ebeln ).
      WHEN 'UB'. " Stock Transport Order
        ro_po = NEW zcl_po_stock_trans( iv_ebeln ).
      WHEN 'SC'. " Subcontract
        ro_po = NEW zcl_po_subcon( iv_ebeln ).
      WHEN OTHERS.
        " fallback to base? But base is abstract; so create standard
        ro_po = NEW zcl_po_standard( iv_ebeln ).
    ENDCASE.
  ENDMETHOD.
ENDCLASS.

Now the calling code is simplified and decoupled:

DATA(lo_po) = zcl_po_factory=>create_po( '4500000125' ).
IF lo_po->validate( ) = abap_true.
  ...

If a new PO type emerges, only the factory changes; the rest of the system remains untouched. This is clean architecture.

5. Exception Classes – Throwing and Catching Properly

5.1 Moving Beyond SY-SUBRC and MESSAGE

Traditional ABAP uses IF sy-subrc <> 0 and MESSAGE e TYPE 'E' for error handling, which mixes user interface with logic. Object-oriented ABAP uses exception classes that extend CX_ROOT. The hierarchy:

  • CX_STATIC_CHECK – must be declared in method signature; checked at compile time.
  • CX_DYNAMIC_CHECK – declared but checked at runtime; can be propagated without declaration if uncaught.
  • CX_NO_CHECK – not required to be declared; for catastrophic errors.

Arjun creates a custom exception class ZCX_PO_ERROR inheriting from CX_STATIC_CHECK. He adds attributes like MV_EBELN and MV_MESSAGE to carry error context.

5.2 Throwing and Catching in the PO Factory

METHOD create_po.
    SELECT SINGLE bsart FROM ekko WHERE ebeln = @iv_ebeln INTO @DATA(lv_bsart).
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_po_error
        EXPORTING
          textid   = zcx_po_error=>po_not_found
          mv_ebeln = iv_ebeln.
    ENDIF.
    ...
ENDMETHOD.

The caller can catch:

TRY.
    DATA(lo_po) = zcl_po_factory=>create_po( '123' ).
  CATCH zcx_po_error INTO DATA(lx_error).
    MESSAGE lx_error->get_text( ) TYPE 'E'.
ENDTRY.

This separates error handling from happy-path logic, making the code more readable and maintainable. Arjun uses exception classes throughout his new dashboard.

6. ABAP Unit Testing – Building Quality In

6.1 The Philosophy of Test-Driven Development (TDD) in ABAP

Arjun’s mentor insists on writing unit tests before or alongside code. ABAP Unit allows you to write test classes in the same class (as local test classes) or in dedicated test includes. Tests are executed via Ctrl+Shift+F10 in ADT or SE24 → Test. The framework provides test doubles via interfaces, as Arjun designed with ZIF_PO_DATA_PROVIDER.

He creates a test class LTC_PO_FACTORY for the factory:

CLASS ltc_po_factory DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
  PRIVATE SECTION.
    METHODS: test_standard_po FOR TESTING,
             test_invalid_po FOR TESTING.
ENDCLASS.

CLASS ltc_po_factory IMPLEMENTATION.
  METHOD test_standard_po.
    " Arrange: use a mock data provider that returns a standard PO BSART
    " Actually, factory reads from database; to make it testable, we could inject a DB access interface.
    " For now, assume the test system has a known PO with BSART 'NB'.
    DATA(lo_po) = zcl_po_factory=>create_po( '4500000001' ). " known test PO
    cl_abap_unit_assert=>assert_bound( lo_po ).
    cl_abap_unit_assert=>assert_equals( exp = abap_true act = lo_po->validate( ) ).
  ENDMETHOD.

  METHOD test_invalid_po.
    TRY.
        zcl_po_factory=>create_po( '0000000000' ).
        cl_abap_unit_assert=>fail( msg = 'Exception expected' ).
      CATCH zcx_po_error.
        " expected
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

Test Double: He refactors the factory to accept an optional ZIF_PO_DATA_PROVIDER for data access. In production, a real DB provider is used; in test, a mock provider returns a predefined BSART. This makes the tests independent of database content. He defines:

CLASS zcl_po_factory DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS set_data_provider IMPORTING io_provider TYPE REF TO zif_po_data_provider.
    CLASS-METHODS create_po IMPORTING iv_ebeln TYPE ebeln RETURNING VALUE(ro_po) TYPE REF TO zcl_po_base.
  PRIVATE SECTION.
    CLASS-DATA mo_provider TYPE REF TO zif_po_data_provider.
ENDCLASS.

In test setup, he injects a mock that returns the desired BSART. This is true OO testability.

7. The Complete Hands-On Lab – Building a Modular Procurement Dashboard

7.1 Requirements

The CFO wants a dashboard with three sections: Open POs (count and total value), Urgent POs (those with delivery date within 7 days), and Vendor Performance (on-time delivery rate). Each section should be pluggable — meaning they can be reordered or replaced easily. Arjun designs a common interface ZIF_DASHBOARD_SECTION with method RENDER returning a simple structure of key-value pairs (or an HTML fragment for later). He’ll then create concrete classes for each section, all implementing the interface. The dashboard class ZCL_DASHBOARD holds a list of ZIF_DASHBOARD_SECTION references and calls RENDER on each.

7.2 Interface and Section Implementations

INTERFACE zif_dashboard_section.
  METHODS render RETURNING VALUE(rt_data) TYPE ztt_dash_data.
ENDINTERFACE.

ZCL_OPEN_PO_SECTION implements it, using ZIF_PO_DATA_PROVIDER to fetch open POs. Similarly ZCL_URGENT_PO_SECTION and ZCL_VENDOR_PERF_SECTION.

The dashboard class:

CLASS zcl_dashboard DEFINITION.
  PUBLIC SECTION.
    METHODS: add_section IMPORTING io_section TYPE REF TO zif_dashboard_section,
             display.
  PRIVATE SECTION.
    DATA: mt_sections TYPE TABLE OF REF TO zif_dashboard_section.
ENDCLASS.

This is the Strategy pattern — the dashboard can have any set of sections, easily configurable. In the constructor or via a configuration class, Arjun adds the required sections. Then display loops over mt_sections and outputs each.

7.3 Putting It All Together in a Report

A simple report ZR_DASHBOARD creates the dashboard, injects the real data provider, adds sections, and displays:

DATA(lo_dash) = NEW zcl_dashboard( ).
lo_dash->add_section( NEW zcl_open_po_section( ) ).
lo_dash->add_section( NEW zcl_urgent_po_section( ) ).
lo_dash->add_section( NEW zcl_vendor_perf_section( ) ).
lo_dash->display( ).

If later the CFO wants a section for “Blocked Invoices”, Arjun only needs to create a new class implementing the interface and add it — no changes to existing code. This is the Open/Closed principle (open for extension, closed for modification). He demonstrates with a quick new class ZCL_BLOCKED_INVOICE_SECTION and drops it in.

8. Design Patterns in ABAP – Real-World Examples

8.1 Singleton Pattern for Data Provider

To avoid multiple database connections, Arjun makes the data provider a singleton. He uses a static attribute and a get_instance method:

CLASS zcl_db_po_provider DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS get_instance RETURNING VALUE(ro_instance) TYPE REF TO zcl_db_po_provider.
    INTERFACES zif_po_data_provider.
  PRIVATE SECTION.
    CLASS-DATA mo_instance TYPE REF TO zcl_db_po_provider.
ENDCLASS.

CLASS zcl_db_po_provider IMPLEMENTATION.
  METHOD get_instance.
    IF mo_instance IS NOT BOUND.
      mo_instance = NEW zcl_db_po_provider( ).
    ENDIF.
    ro_instance = mo_instance.
  ENDMETHOD.
ENDCLASS.

Now all sections use the same provider instance.

8.2 Observer Pattern via ABAP Events

When a PO is created or changed, several parts of the system might need to react (inventory, accounting, dashboard refresh). Instead of hard coupling, Arjun defines an event PO_CHANGED in ZCL_PO_BASE (or a separate event manager). Other classes register as handlers. He shows a simple example: the dashboard section ZCL_OPEN_PO_SECTION registers to the event and refreshes its internal cache when a PO changes.

CLASS zcl_po_base DEFINITION ABSTRACT.
  PUBLIC SECTION.
    EVENTS po_changed EXPORTING VALUE(ebeln) TYPE ebeln.
    ...
ENDCLASS.

In the dashboard section:

SET HANDLER me->on_po_changed FOR ALL INSTANCES.

This decouples the notification from the reaction.

9. Debugging OO ABAP – Tips and Tricks

In debugger, you can inspect object references, view attributes, and even call methods interactively. Arjun shows how to set a breakpoint in a method, step through, and use the “Object” display to see all attributes. He also demonstrates the class hierarchy in the debugger, which shows the actual class type (e.g., ZCL_PO_STANDARD even if the variable is typed as ZCL_PO_BASE). This is invaluable for understanding polymorphism at runtime.

10. Pros and Cons of OO ABAP vs Procedural

OO ABAPProcedural ABAP
Modular, reusable classesLong programs, hard to reuse
Encapsulation prevents unintended data accessGlobal variables prone to side effects
Inheritance and interfaces enable extensionLimited to copy-paste for variants
Testable with ABAP Unit and mocksDifficult to unit test; usually manual
Steeper learning curveEasier initial learning

For any non-trivial development, OO wins on maintainability. Arjun’s dashboard code is half the size of the old procedural report and infinitely more flexible.

11. Common Mistakes and How to Avoid Them

  • Forgetting to declare methods as PUBLIC: Leads to “Method not found” errors. Always check the visibility.
  • Circular dependencies: Class A depends on B, B depends on A. Use interfaces to break cycles.
  • Over-engineering: Not every 50-line program needs a full OO hierarchy. Start simple, refactor when complexity grows.
  • Ignoring garbage collection: ABAP handles it, but large object graphs can cause memory issues. Use weak references if needed.

12. Conclusion – The OO Mindset

Arjun’s procurement dashboard is now live, and the CFO is delighted. More importantly, the codebase is clean, tested, and ready for future changes. The old 5000-line include has been retired. He has embraced the OO mindset: thinking in terms of objects, responsibilities, and contracts. This is the same mindset that underlies SAP’s own modern frameworks like RAP, BOPF, and Fiori.

Tomorrow, in Part 37, we’ll enter the world of Core Data Services (CDS) Views — the heart of S/4HANA’s virtual data model. You’ll build CDS views with associations, annotations, and access controls, paving the way for OData services and Fiori apps. The architecture you learned today will seamlessly extend into the CDS world.

Keep designing, keep coding.

End of Part 36 – Object-Oriented ABAP Masterclass. Brought to you by @FreeLearning365 and tech partner @techbook24.

Post a Comment

0 Comments