Skip to Content
Technical Articles
Author's profile photo Johannes Gerbershagen

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.

Assigned Tags

      6 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Michelle Crapo
      Michelle Crapo

      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.

       

      Author's profile photo Gábor Márián
      Gábor Márián

      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( ) ).  
      
      Author's profile photo Johannes Gerbershagen
      Johannes Gerbershagen
      Blog Post Author

      Good idea, i didn’t thought of it before. I added it to the blog-post.

      Author's profile photo Michael Keller
      Michael Keller

      Hi Johannes, thanks that you wrote a blog about this topic. I've been working on my understanding of the topic for some time 🙂

      Author's profile photo Sandra Rossi
      Sandra Rossi

      Eventually people may have a quick look at chapter Testing > Injection of ABAP Clean Code.

      Author's profile photo Tanmoy Mondal
      Tanmoy Mondal

       

      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