Skip to Content
Technical Articles
Author's profile photo Michael Keller

dependency injection – example with fire

Dear community, I’m always looking for easy to understand examples of how to do certain things.That helps me with my understanding and it helps me when I want to explain it to students and trainees, for example. I recently looked at the subject of “dependency injection” again. In that context, I thought about a nice example from real life. The result is an example with “fire“.

In order for a fire to arise or exist, certain requirements must be met. There is a nice figure about this on Wikipedia. As shown in the figure, three factors are important:

  1. fuel
  2. oxygen
  3. heat

If any of these factors are absent or if one of these factors is unbalanced, there is no fire. You learn that at an early age as a firefighter. So here we have some real dependencies ๐Ÿ™‚

Ok, then we want to map that example to ABAP classes. Let’s start.

First of all there is a class ZCL_FUEL, which uses its attribute MV_FLAMMABLE to indicate whether a substance is flammable.

CLASS zcl_fuel DEFINITION
               PUBLIC
               FINAL
               CREATE PUBLIC.

  PUBLIC SECTION.
    DATA mv_flammable TYPE abap_bool READ-ONLY.

    METHODS constructor
      IMPORTING
        iv_flammable TYPE abap_bool.
ENDCLASS.

CLASS zcl_fuel IMPLEMENTATION.
  METHOD constructor.
    mv_flammable = iv_flammable.
  ENDMETHOD.
ENDCLASS.

The next class is ZCL_OXYGEN, which uses its attribute MV_SUFFICIENT_QUANTITY to indicate whether there is enough oxygen for a fire.

CLASS zcl_oxygen DEFINITION
                 PUBLIC
                 FINAL
                 CREATE PUBLIC.

  PUBLIC SECTION.
    DATA mv_sufficient_quantity TYPE abap_bool READ-ONLY.

    METHODS constructor
      IMPORTING
        iv_sufficient_quantity TYPE abap_bool.
ENDCLASS.

CLASS zcl_oxygen IMPLEMENTATION.
  METHOD constructor.
    mv_sufficient_quantity = iv_sufficient_quantity.
  ENDMETHOD.
ENDCLASS.

The next class is ZCL_HEAT. Sufficient heat is required for a fire to start or exist.

CLASS zcl_heat DEFINITION
               PUBLIC
               FINAL
               CREATE PUBLIC.

  PUBLIC SECTION.
    DATA mv_sufficient_heat TYPE abap_bool READ-ONLY.

    METHODS constructor
      IMPORTING
        iv_sufficient_heat TYPE abap_bool.
ENDCLASS.

CLASS zcl_heat IMPLEMENTATION.
  METHOD constructor.
    mv_sufficient_heat = iv_sufficient_heat.
  ENDMETHOD.
ENDCLASS.

The three classes mentioned above play a role for the ZCL_FIRE class. This class can be used to illustrate a “constructor injection” and a “setter injection“. In practice, one would only use constructor injection. According to Clean ABAP, setter injection isn’t recommended. Regardless, that’s how it should be for the example ๐Ÿ˜‰

CLASS zcl_fire DEFINITION
               PUBLIC
               FINAL
               CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS constructor
      IMPORTING
        io_fuel   TYPE REF TO zcl_fuel OPTIONAL
        io_oxygen TYPE REF TO zcl_oxygen OPTIONAL
        io_heat   TYPE REF TO zcl_heat OPTIONAL.

    METHODS set_fuel
      IMPORTING
        io_fuel TYPE REF TO zcl_fuel.

    METHODS set_oxygen
      IMPORTING
        io_oxygen TYPE REF TO zcl_oxygen.

    METHODS set_head
      IMPORTING
        io_heat TYPE REF TO zcl_heat.

    METHODS is_burning
      RETURNING VALUE(rv_result) TYPE abap_bool.

  PRIVATE SECTION.
    DATA mo_fuel TYPE REF TO zcl_fuel.
    DATA mo_oxygen TYPE REF TO zcl_oxygen.
    DATA mo_heat TYPE REF TO zcl_heat.
ENDCLASS.

CLASS zcl_fire IMPLEMENTATION.
  METHOD constructor.
    mo_fuel = io_fuel.
    mo_oxygen = io_oxygen.
    mo_heat = io_heat.
  ENDMETHOD.

  METHOD is_burning.
    IF mo_fuel->mv_flammable = abap_true AND
       mo_oxygen->mv_sufficient_quantity = abap_true AND
       mo_heat->mv_sufficient_heat = abap_true.
      rv_result = abap_true.
    ENDIF.
  ENDMETHOD.

  METHOD set_fuel.
    mo_fuel = io_fuel.
  ENDMETHOD.

  METHOD set_head.
    mo_heat = io_heat.
  ENDMETHOD.

  METHOD set_oxygen.
    mo_oxygen = io_oxygen.
  ENDMETHOD.
ENDCLASS.

Finally, a couple of ABAP unit tests. The fire only burns if the three factors fuel, oxygen and heat with appropriate properties or in sufficient quantities are available. The prefix “CI” stands for “constructor injection” and the prefix “SI” stands for “setter injection”.

CLASS ltc_fire DEFINITION FOR TESTING DURATION LONG RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    METHODS ci_fire_when_everything_fits FOR TESTING.
    METHODS ci_no_fire_not_enough_heat FOR TESTING.
    METHODS si_fire_when_everything_fits FOR TESTING.
    METHODS si_no_fire_not_enough_heat FOR TESTING.
ENDCLASS.

CLASS ltc_fire IMPLEMENTATION.
  METHOD ci_fire_when_everything_fits.
    DATA(lo_fuel) = NEW zcl_fuel( iv_flammable = abap_true ).
    DATA(lo_oxygen) = NEW zcl_oxygen( iv_sufficient_quantity = abap_true ).
    DATA(lo_heat) = NEW zcl_heat( iv_sufficient_heat = abap_true ).

    DATA(lo_fire) = NEW zcl_fire( io_fuel   = lo_fuel
                                  io_oxygen = lo_oxygen
                                  io_heat   = lo_heat ).

    DATA(lv_is_burning) = lo_fire->is_burning( ).

    cl_abap_unit_assert=>assert_true( act = lv_is_burning ).
  ENDMETHOD.

  METHOD ci_no_fire_not_enough_heat.
    DATA(lo_fuel) = NEW zcl_fuel( iv_flammable = abap_true ).
    DATA(lo_oxygen) = NEW zcl_oxygen( iv_sufficient_quantity = abap_true ).
    DATA(lo_heat) = NEW zcl_heat( iv_sufficient_heat = abap_false ).

    DATA(lo_fire) = NEW zcl_fire( io_fuel   = lo_fuel
                                  io_oxygen = lo_oxygen
                                  io_heat   = lo_heat ).

    DATA(lv_is_burning) = lo_fire->is_burning( ).

    cl_abap_unit_assert=>assert_false( act = lv_is_burning ).
  ENDMETHOD.

  METHOD si_fire_when_everything_fits.
    DATA(lo_fuel) = NEW zcl_fuel( iv_flammable = abap_true ).
    DATA(lo_oxygen) = NEW zcl_oxygen( iv_sufficient_quantity = abap_true ).
    DATA(lo_heat) = NEW zcl_heat( iv_sufficient_heat = abap_true ).

    DATA(lo_fire) = NEW zcl_fire( ).
    lo_fire->set_fuel( lo_fuel ).
    lo_fire->set_head( lo_heat ).
    lo_fire->set_oxygen( lo_oxygen ).

    DATA(lv_is_burning) = lo_fire->is_burning( ).

    cl_abap_unit_assert=>assert_true( act = lv_is_burning ).
  ENDMETHOD.

  METHOD si_no_fire_not_enough_heat.
    DATA(lo_fuel) = NEW zcl_fuel( iv_flammable = abap_true ).
    DATA(lo_oxygen) = NEW zcl_oxygen( iv_sufficient_quantity = abap_true ).
    DATA(lo_heat) = NEW zcl_heat( iv_sufficient_heat = abap_false ).

    DATA(lo_fire) = NEW zcl_fire( ).
    lo_fire->set_fuel( lo_fuel ).
    lo_fire->set_head( lo_heat ).
    lo_fire->set_oxygen( lo_oxygen ).

    DATA(lv_is_burning) = lo_fire->is_burning( ).

    cl_abap_unit_assert=>assert_false( act = lv_is_burning ).
  ENDMETHOD.
ENDCLASS.

That’s my example on the subject of “dependency injection”. Now some questions to the community:

  1. What do you think about the example? Is it easy to understand?
  2. Can you recommend a better one?
  3. Is it sufficient to implement the “dependency inversion principle” by changing the IMPORTING parameters of the constructor to use interfaces?

 

Best regards, thanks for reading and please stay healthy

Michael

 

P.S.: Please support the virtual wishing well.

P.S.S.: Not tired of reading blogs? Check this blog by Jรถrg Krause.

Assigned Tags

      10 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Marco Beier
      Marco Beier

      Hey Michael,

      really liking this blog and the idea of making development concepts more accessible and easier to understand by e.g. using real world examples for actual dependencies!

      1. I definitely think it was easy to understand and shows how simple one can imagine the "mysterious" term 'dependency injection' and what it represents.
      2. I'd have to think a bit longer about that one ๐Ÿ˜‰ probably none so easy to understand as yours.
      3. I would say so (plus changing the respective types of the class attributes in fire of course and implementing those interfaces for heat, fuel & oxygen)

      Keep up sharing those nice blogs & resources!

      BR, Marco =)

      Author's profile photo Michael Keller
      Michael Keller
      Blog Post Author

      Hi Marco, thanks for your comment! It's great to have some feedback ๐Ÿ™‚

      Author's profile photo Enno Wulff
      Enno Wulff

      Hey Michael,

      I also like the approach of finding different ways to explain something. IMHO this example shows dependency Injection, but it does not show the power of it.

      In your case the dependency is initially there. Dependency injection - from my point of view/ knowledge - is something additional. Something you can do for special cases. Mostly I think for unit tests.

       

      jm2c

      ~Enno

      Author's profile photo Michael Keller
      Michael Keller
      Blog Post Author

      Hi Enno, thanks for your feedback. Perhaps an example with refactoring could be what you mean? Something like "initially, there is no constructor injection possible and after refactoring, it's possible"?

      Author's profile photo Enno Wulff
      Enno Wulff

      I just read some definitions and examples for DI and the more I read the more i come to the conclusion that your example is wrong, beacuse the DI wants to decouple dependencies. You have a straight dependency between your main class and the three object classes...

      A DI needs an interface, because this is the main reason for achieving an inversion of control. That means that the calling class just knows the interface and the caller has to (or can) decide, what attributes the object has. These attributes are fixed in your example as they are classes (instead of an interface)

      Adapted to your example, it might look like follows:

       

      the main class retrieves the amount of oxygen and the burning point of the liquid of some kind of transort unit. A temperature sensor delivers the current temperature. the main class then caculates, if it will burn. (at least a quite useless function, as if all the current parameters will cause a fire, it already would burn... ๐Ÿ˜€ )

       

      The test class has pre-defined containers with reasonable attributes to test the correct calculation.

      INTERFACE _obj.
        METHODS flammable_temperature RETURNING VALUE(result) TYPE i.
        METHODS amount_of_oxygen RETURNING VALUE(result) TYPE i.
        METHODS temperature RETURNING VALUE(result) TYPE i.
        DATA temp TYPE i.
        DATA temp_flamm TYPE i.
        DATA oxy TYPE i.
      ENDINTERFACE.
      
      CLASS transport_unit DEFINITION.
        PUBLIC SECTION.
          INTERFACES _obj.
          METHODS constructor IMPORTING unit_no TYPE i.
      ENDCLASS.
      
      CLASS liquid_1 DEFINITION.
        PUBLIC SECTION.
          INTERFACES _obj.
      ENDCLASS.
      
      CLASS main DEFINITION.
        PUBLIC SECTION.
          METHODS constructor IMPORTING obj TYPE REF TO _obj OPTIONAL.
          METHODS is_burning RETURNING VALUE(it_burns) TYPE abap_bool.
        PRIVATE SECTION.
          DATA myobject TYPE REF TO _obj.
      ENDCLASS.
      
      CLASS main IMPLEMENTATION.
        METHOD constructor.
          IF obj IS SUPPLIED.
            myobject = obj.
          ELSE.
            myobject ?= NEW transport_unit( 4711 ).
          ENDIF.
        ENDMETHOD.
        METHOD is_burning.
          IF myobject->amount_of_oxygen( ) > 5.
            IF myobject->temperature( ) > myobject->flammable_temperature( ).
              it_burns = abap_true.
            ELSE.
              it_burns = abap_false.
            ENDIF.
          ELSE.
            it_burns = abap_false.
          ENDIF.
      
        ENDMETHOD.
      ENDCLASS.
      
      
      CLASS liquid_1 IMPLEMENTATION.
        METHOD _obj~flammable_temperature.
          result = 45.
        ENDMETHOD.
        METHOD _obj~amount_of_oxygen.
          result = 15.
        ENDMETHOD.
        METHOD _obj~temperature.
          result = 34.
        ENDMETHOD.
      ENDCLASS.
      
      
      CLASS transport_unit IMPLEMENTATION.
        METHOD constructor.
          "select temp_flammable, oxygen from somewehere
          "into (temp_flamm, oxy)
          "where unit_number = unit_no.
          _obj~temp_flamm  = 23.
          _obj~oxy         = 34.
      
          "temp = sensor->get_current_temperature( unit_no ).
          _obj~temp        = 32.
        ENDMETHOD.
      
        METHOD _obj~flammable_temperature.
          result = _obj~temp_flamm.
        ENDMETHOD.
        METHOD _obj~amount_of_oxygen.
          result = _obj~oxy.
        ENDMETHOD.
        METHOD _obj~temperature.
          result = _obj~temp.
        ENDMETHOD.
      
      ENDCLASS.
      
      CLASS test_burning DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
        PRIVATE SECTION.
          METHODS test_liquid_1 FOR TESTING.
      ENDCLASS.
      
      CLASS test_burning IMPLEMENTATION.
        METHOD test_liquid_1.
          cl_abap_unit_assert=>assert_equals(
            EXPORTING
              act  = NEW main( NEW liquid_1( ) )->is_burning( )
              exp  = abap_false
              msg  = 'it should not burn'
          ).
        ENDMETHOD.
      ENDCLASS.
      
      START-OF-SELECTION.
        DATA(appl) = NEW main( ).
        MESSAGE |object is burning { SWITCH #( appl->is_burning( ) WHEN abap_true THEN 'Yes' ELSE 'No' ) }| TYPE 'I'.

       

       

      Author's profile photo Michael Keller
      Michael Keller
      Blog Post Author

      Ah, I thought for dependency injection via construtor, classes are ok. Better would be interfaces but that would be already the dependency inversion principle?

      Author's profile photo Enno Wulff
      Enno Wulff

      Ah, I thought for dependency injection via construtor, classes are ok

      This is a dependency. but there is no dependency injection, just because the objects are being imported. From my point of view (and I may be wrong!) the injection is only possible by using an interface where the called object does not know the specific characteristics of the given class.

      Dependency inversion is something different and does not depend on "having interfaces or not".

      Author's profile photo Enno Wulff
      Enno Wulff

      a helpful blog in german:

      https://www.microsoft.com/de-de/techwiese/know-how/was-ist-eigentlich-dependency-injection.aspx

      that shows that the main thing is decoupling.

      Author's profile photo Andreas Gautsch
      Andreas Gautsch

      Hi Michael!

      Thank you for the example, I like it a lot more for explaining dependency injection (DI) like the typical car and fruit examples.

      But as Enno stated I also think the power of DI is not entirely highlighted.

      Your โ€œfire worksโ€ but its not yet sustainable,ย  try to switch to a renewable material like wood. You would have to adapt your fire class which higly depends on the class fuel(oil) *).

      If you inject (by constructor or by setter) an interface instead, for example ZIF_MATERIAL then you can switch your fire from oil to wood without the need to adapt the fire class โ€“ thats sustainabilty enabled by dependency injection.

      Greetings, Andreas

      *) for simplicity reason I switched from fuel to oil, as I am not sure if wood is a type of fuel. Well but thats only a language topic, and oil plays better fits in my sustainability approach ?

      Author's profile photo Michael Keller
      Michael Keller
      Blog Post Author

      Hi Andreas,

      thanks for the notes and hints! It seems there is a new example useful to blog about in the future ๐Ÿ™‚ For now, I learned a lot by the comments and thoughts from you and Enno.

      Best regards, Michael