Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
In the literature the dependency inversion principle is often presented as a very abstract principle. High level classes must not depend on low level classes is the first phrase, the clean-code-developer initiative used to explain this principle. Especially in the ABAP procedural world, it's very rare, to find some good use cases for this principle. But in my recent projects, i got a good use case, which helped me understanding the phrase High level classes must not depend on low level classes a lot. The use case was a TCO (total cost of ownership) calculator. The TCO was composed from the purchasing price and some surcharges. Different material types had a different surcharging calculation algorithm.
I started implementing the calculation algorithm with TDD (test-driven-development). My first draft was the following:


CLASS ztco_calculator DEFINITION FINAL.

PUBLIC SECTION.
TYPES: BEGIN OF tco_and_surcharges,
tco TYPE ztco,
surcharges_logistics TYPE zsurcharges_logistics,
process_costs TYPE zprocess_costs,
END OF tco_and_surcharges.

METHODS constructor
IMPORTING
purchase_order_item TYPE ekpo.

METHODS calculate_tco_with_surcharges
RETURNING VALUE(result) TYPE tco_and_surcharges.

PROTECTED SECTION.
DATA: purchase_order_item TYPE ekpo.

METHODS get_purchasing_price
RETURNING VALUE(result) TYPE zpurchase_price.

METHODS get_surcharges_logistics
RETURNING VALUE(result) TYPE zsurcharges_logistics.

METHODS get_process_costs
RETURNING VALUE(result) TYPE zprocess_costs.

ENDCLASS.

CLASS ztco_calculator IMPLEMENTATION.

METHOD constructor.
me->purchase_order_item = purchase_order_item.
ENDMETHOD.

METHOD calculate_tco_with_surcharges.
result-surcharges_logistics = get_surcharges_logistics( ).
result-process_costs = get_process_costs( ).
result-tco = get_purchasing_price( ) +
result-surcharges_logistics +
result-process_costs.
ENDMETHOD.

ENDCLASS.



The second draft was test-friendlier. After moving the methods get_surcharges_logistics and get_process_costs (process-costs were some kind of surcharges) in an interface, i could stub them in the unit-test.


INTERFACE ztco_surcharge_calculator.
METHODS get_surcharges_logistics
RETURNING VALUE(result) TYPE zsurcharges_logistics.

METHODS get_process_costs
RETURNING VALUE(result) TYPE zprocess_costs.
ENDINTERFACE.

CLASS ztco_calculator DEFINITION FINAL.

PUBLIC SECTION.

METHODS constructor
IMPORTING
surcharge_calculator TYPE REF TO ztco_surcharge_calculator
purchase_order_item TYPE ekpo.

METHODS calculate_tco_with_surcharges
RETURNING VALUE(result) TYPE tco_and_surcharges.

PROTECTED SECTION.
DATA: surcharge_calculator TYPE REF TO ztco_surcharge_calculator,
purchase_order_item TYPE ekpo.

METHODS get_purchasing_price
RETURNING VALUE(result) TYPE zpurchase_price.


ENDCLASS.

CLASS ztco_calculator IMPLEMENTATION.

METHOD constructor.
me->surcharge_calculator = surcharge_calculator.
me->purchase_order_item = purchase_order_item.
ENDMETHOD.

METHOD calculate_tco_with_surcharges.
result-surcharges_logistics = surcharge_calculator->get_surcharges_logistics( ).
result-process_costs = surcharge_calculator->get_process_costs( ).
result-tco = get_purchasing_price( ) +
result-surcharges_logistics +
result-process_costs.
ENDMETHOD.

ENDCLASS.



Assemble the whole thing


In the first step the different surcharging calculation algorithms must be created. They must all implement the interface ztco_surcharge_calculator. We had the following three classes:

  • ztco_surcharges_engine_oil

  • ztco_surcharges_ball_bearing

  • ztco_surcharges_default


But we don't use them in the main-class ztco_calculator. Instead of we just used the interface ztco_surcharge_calculator. This works with a technique called polymorphism.

In the second step a interface reference variable for the material type was created and instantiated.
After the second step, the main-class ztco_calculator could be created.


DATA: sucharge_calculator TYPE REF TO ztco_surcharge_calculator,
material_type TYPE mtart.
SELECT SINGLE mtart INTO material_type FROM mara WHERE matnr = purchase_order_item-matnr.
CASE material_type.
WHEN 'EOIL'.
" engine oil
surcharge_calculator = NEW ztco_surcharges_engine_oil( ).
WHEN 'BEAR'.
" ball bearing
surcharge_calculator = NEW ztco_surcharges_ball_bearing( ).
OTHERS.
surcharge_calculator = NEW ztco_surcharges_default( ).
ENDCASE.

DATA(tco_calculator) = NEW ztco_calculator(
surcharge_calculator = surcharge_calculator
purchase_order_item = purchase_order_item ).
cl_demo_output=>display( tco_calculator->calculate_tco_with_surcharges( ) ).



To make it more configurable, we could even create a customizing-table, which links the material type and the surcharging calculation algorithm. The interface reference variable can then be created dynamic (Idea from Gábor Márián).

Conclusion


Do you realize how the dependency between the surcharging calculation algorithms and the TCO calculation was minimized in the second draft? In the first draft, the different surcharging calculation algorithms must be squeezed in two methods. These would cause at least the anti-pattern code-duplication (CASE-condition).

In the second draft the different surcharging calculation algorithms are separated. The main-class ztco_calculator only needs two interface methods.
This draft adds flexibility for enhancements or revisions, the main goal of the dependency inversion principle.
6 Comments