Technical Articles
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:
- fuel
- oxygen
- 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:
- What do you think about the example? Is it easy to understand?
- Can you recommend a better one?
- 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.
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!
Keep up sharing those nice blogs & resources!
BR, Marco =)
Hi Marco, thanks for your comment! It's great to have some feedback ๐
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
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"?
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.
Ah, I thought for dependency injection via construtor, classes are ok. Better would be interfaces but that would be already the dependency inversion principle?
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".
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.
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 ?
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