Introduction: Applying Design Principles in ABAP
In ABAP development, many of us don’t always focus on design principles and patterns, especially since our work often revolves around customizing existing solutions or integrating systems rather than building software from scratch. However, understanding and applying these principles can still greatly improve the quality, maintainability, and scalability of our code.
In this blog, I want to share a practical example of how we can apply the Dependency Inversion principle, a key concept in the SOLID design patterns, in our everyday ABAP tasks.
What is Dependency Inversion?
Dependency Inversion suggests that the high-level modules (which contain the core logic of the application) should not rely directly on low-level modules (which handle specific tasks or data). Instead, both should depend on abstractions.
Example Scenario: Checking the Budget
Let’s imagine that we are working on a system where we need to check the budget for a Purchase Order (PO). Here’s how the process might unfold in a typical setup without considering Dependency Inversion:
Step 1: Create a PO class that has a method Check_PO_Budget().Step 2: Create a BudgetService class that interacts with the PO class.Step 3: In the BudgetService class, you instantiate a PO object and call the po_obj->Check_PO_Budget() method.
The Problem with the Initial Approach
In the example above, the BudgetService class directly depends on the PO class to check the budget. the low and high levels are tightly coupled. The BudgetService class cannot function without initiating a PO object. Another thing f you need to add new functionality, such as checking the budget for a Sales Order (SO) instead of a PO, you have to modify the BudgetService class itself .
Applying Dependency Inversion to Solve the Problem
To solve these issues, we need to decouple the high-level module (BudgetService) from the low-level module (PO). both the high and low-level modules should depend on an interface ( Abstraction layer) that defines the behavior they require, rather than depending directly on each other.
Step-by-Step Approach:
Create an Interface: Define an interface that includes the method for checking the budget.
INTERFACE zi_budge_services PUBLIC .
METHODS :
Check_budget RETURNING VALUE(r_out1) type string.
ENDINTERFACE.
Implement the Interface in the PO Class
CLASS zcl_po DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES zi_budge_services.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_po IMPLEMENTATION.
METHOD zi_budge_services~check_budget.
r_out1 = ‘Check budget for PO!’ .
ENDMETHOD.
ENDCLASS.
Create the High-Level Class: The BudgetService class will depend on the interface rather than the concrete PO class.
Note that : in the instructor we a type of the Interface so that we can inject any object or class that implements the interface and use its own functionality of checking the budget . this is a technique used to apply the Dependency Inversion called dependency injection ( Constructor Injection ).
CLASS zcl_access_budget_service DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
methods constructor
IMPORTING !budget_service type REF TO zi_budge_services .
methods : run_budget_services RETURNING VALUE(r_output) type string .
PROTECTED SECTION.
PRIVATE SECTION.
data : budget_service type REF TO zi_budge_services .
ENDCLASS.
CLASS zcl_access_budget_service IMPLEMENTATION.
METHOD constructor.
me->budget_service = budget_service .
ENDMETHOD.
METHOD run_budget_services.
data(o1) = budget_service->check_budget( ) .
r_output = o1 .
ENDMETHOD.
ENDCLASS.
Test the logic:
CLASS ztest_di DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun .
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS ztest_di IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lo_po) = NEW zcl_po( ) .
data(lo_budget1) = new zcl_access_budget_service( lo_po ) .
data(output) = lo_budget1->run_budget_services( ) .
ENDMETHOD.
ENDCLASS.
Introducing New Functionalities Without Modifying High-Level Modules : The high level BudgetService remains untouched. We will add a new budget checker (SO) then just Inject it into the BudgetService .
CLASS zcl_so DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES zi_budge_services.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_so IMPLEMENTATION.
METHOD zi_budge_services~check_budget.
r_out1 = ‘Check budget for SO!’ .
ENDMETHOD.
ENDCLASS.
Nothing to be changed in the BudgetService main Class and only we Inject the SO Object in the testing class like below
DATA(lo_so) = NEW zcl_so( ) .
data(lo_budget1) = new zcl_access_budget_service( lo_so ) .
data(output) = lo_budget1->run_budget_services( ) .
Conclusion
In the blog I tried to highlight the benefits of Dependency Inversion with Dependency Injection as below :
Decoupling: The high-level BudgetService class is now independent of the low-level PO and SO classes. This makes it easier to change or replace them without affecting the core logic.Flexibility: New types of budget checkers (like PO_V2 or SO) can be introduced with minimal impact on the existing system.Easier Maintenance: Changes in the low-level classes (e.g., the PO class) do not require changes in the high-level module (e.g., the BudgetService class), reducing the risk of introducing bugs.
This is my first time to work with this pattern and I wanted to share what I have learned , So feel free to try it out in your projects and let me know your thoughts.
Introduction: Applying Design Principles in ABAP In ABAP development, many of us don’t always focus on design principles and patterns, especially since our work often revolves around customizing existing solutions or integrating systems rather than building software from scratch. However, understanding and applying these principles can still greatly improve the quality, maintainability, and scalability of our code.In this blog, I want to share a practical example of how we can apply the Dependency Inversion principle, a key concept in the SOLID design patterns, in our everyday ABAP tasks.What is Dependency Inversion?Dependency Inversion suggests that the high-level modules (which contain the core logic of the application) should not rely directly on low-level modules (which handle specific tasks or data). Instead, both should depend on abstractions.Example Scenario: Checking the BudgetLet’s imagine that we are working on a system where we need to check the budget for a Purchase Order (PO). Here’s how the process might unfold in a typical setup without considering Dependency Inversion:Step 1: Create a PO class that has a method Check_PO_Budget().Step 2: Create a BudgetService class that interacts with the PO class.Step 3: In the BudgetService class, you instantiate a PO object and call the po_obj->Check_PO_Budget() method.The Problem with the Initial ApproachIn the example above, the BudgetService class directly depends on the PO class to check the budget. the low and high levels are tightly coupled. The BudgetService class cannot function without initiating a PO object. Another thing f you need to add new functionality, such as checking the budget for a Sales Order (SO) instead of a PO, you have to modify the BudgetService class itself .Applying Dependency Inversion to Solve the ProblemTo solve these issues, we need to decouple the high-level module (BudgetService) from the low-level module (PO). both the high and low-level modules should depend on an interface ( Abstraction layer) that defines the behavior they require, rather than depending directly on each other.Step-by-Step Approach:Create an Interface: Define an interface that includes the method for checking the budget. INTERFACE zi_budge_services PUBLIC .
METHODS :
Check_budget RETURNING VALUE(r_out1) type string.
ENDINTERFACE. Implement the Interface in the PO Class CLASS zcl_po DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES zi_budge_services.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_po IMPLEMENTATION.
METHOD zi_budge_services~check_budget.
r_out1 = ‘Check budget for PO!’ .
ENDMETHOD.
ENDCLASS. Create the High-Level Class: The BudgetService class will depend on the interface rather than the concrete PO class. Note that : in the instructor we a type of the Interface so that we can inject any object or class that implements the interface and use its own functionality of checking the budget . this is a technique used to apply the Dependency Inversion called dependency injection ( Constructor Injection ). CLASS zcl_access_budget_service DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
methods constructor
IMPORTING !budget_service type REF TO zi_budge_services .
methods : run_budget_services RETURNING VALUE(r_output) type string .
PROTECTED SECTION.
PRIVATE SECTION.
data : budget_service type REF TO zi_budge_services .
ENDCLASS.
CLASS zcl_access_budget_service IMPLEMENTATION.
METHOD constructor.
me->budget_service = budget_service .
ENDMETHOD.
METHOD run_budget_services.
data(o1) = budget_service->check_budget( ) .
r_output = o1 .
ENDMETHOD.
ENDCLASS. Test the logic: CLASS ztest_di DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun .
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS ztest_di IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lo_po) = NEW zcl_po( ) .
data(lo_budget1) = new zcl_access_budget_service( lo_po ) .
data(output) = lo_budget1->run_budget_services( ) .
ENDMETHOD.
ENDCLASS. Introducing New Functionalities Without Modifying High-Level Modules : The high level BudgetService remains untouched. We will add a new budget checker (SO) then just Inject it into the BudgetService . CLASS zcl_so DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES zi_budge_services.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_so IMPLEMENTATION.
METHOD zi_budge_services~check_budget.
r_out1 = ‘Check budget for SO!’ .
ENDMETHOD.
ENDCLASS. Nothing to be changed in the BudgetService main Class and only we Inject the SO Object in the testing class like below DATA(lo_so) = NEW zcl_so( ) .
data(lo_budget1) = new zcl_access_budget_service( lo_so ) .
data(output) = lo_budget1->run_budget_services( ) . ConclusionIn the blog I tried to highlight the benefits of Dependency Inversion with Dependency Injection as below : Decoupling: The high-level BudgetService class is now independent of the low-level PO and SO classes. This makes it easier to change or replace them without affecting the core logic.Flexibility: New types of budget checkers (like PO_V2 or SO) can be introduced with minimal impact on the existing system.Easier Maintenance: Changes in the low-level classes (e.g., the PO class) do not require changes in the high-level module (e.g., the BudgetService class), reducing the risk of introducing bugs.This is my first time to work with this pattern and I wanted to share what I have learned , So feel free to try it out in your projects and let me know your thoughts. Read More Technology Blogs by Members articles
#SAP
#SAPTechnologyblog