Skip to Content
Technical Articles

The dependency inversion principle

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
You must be Logged on to comment or reply to a post.
  • Nice one!

    You make me remember in the middle of learning “things” HANA.  I have a lot to do to brush up my other skills as well.  Balance learning more about classes with learning more about CDS with learning more about BRF+ with learning more about….   OK too much – my head may split into 1000 pieces.  And that is what makes this blog so valuable to me.

    This has taken a difficult topic and explained it nicely.  Exactly what I need.  TDD?  Still not sure about that.  I know I’ll get there at some point.

     

  • I like the result.  But how about making the surcharge calculator configurable, so no source code modification is required when a new algorithm is introduced?

    CLASS surcharge_calculator_factory DEFINITION.
    
      PUBLIC SECTION.
    
        METHODS get_by_material_type
          IMPORTING
            material_type     TYPE mtart
          RETURNING
            VALUE(calculator) TYPE REF TO ztco_surcharge_calculator.
    
    ENDCLASS.
    
    CLASS surcharge_calculator_factory IMPLEMENTATION.
    
      METHOD get_by_material_type.
    
        SELECT SINGLE calculator_class
          FROM ztco_surch_algo
          INTO @DATA(calculator_class)
          WHERE mat_type = material_type.
    
        TRY.
    
            CREATE OBJECT calculator TYPE (calculator_class).
    
          CATCH cx_sy_create_object_error. " Invalid or no configuration
    
            calculator = NEW ztco_surcharges_default( ).
    
        ENDTRY.
    
      ENDMETHOD.
    
    ENDCLASS.
    
    .....
      
    DATA(tco_calculator) = NEW ztco_calculator( 
      surcharge_calculator = surcharge_calculator_factory( )->get_by_material_type( material_type )
      purchase_order_item = purchase_order_item ).
    cl_demo_output=>display( tco_calculator->calculate_tco_with_surcharges( ) ).  
    
  •  

    Yeah, this is good information with a concrete example. Based on my understanding of dependency inversion principle (aka dip), it shows the power of combining abstraction and polymorphism together to achieve a code that is less rigid and fragile. Using Interfaces/Abstract classes and their implementations in sub classes to vary the behavior (i.e. polymorphism) to make your code less susceptible to future change is a good definition of dip.  There is also a good documentation on dip.

    Thank you for the blog.

    https://web.archive.org/web/20150905081103/http://www.objectmentor.com/resources/articles/dip.pdf

    All the best,

    Tanmoy