Dependency Injection

/wp-content/uploads/2014/03/image001_418055.png

There is many a true word, Spoken Inject

One line summary:-

One way to write OO programs with many small classes with less lines of code.

Back Story

The other day there was a blog on SCN about Dependency injection.

http://scn.sap.com/community/abap/blog/2014/01/06/successful-abap-dependency-injection

I thought – I know what that is – if an object (say a car object) needs an engine object to work, you don’t have the car object create the engine object, you pass in the engine object through the car objects constructor.

I had thought that was solely concerned with unit testing, but if you look at the comments at the bottom, when I started talking about this on the blog comments, people soon put me right, it turns out it has a much wider scope.

As soon as I realised I was barking up the wrong tree, I read all I could on the subject, for example …

http://en.wikipedia.org/wiki/Dependency_injection

http://martinfowler.com/articles/injection.html

http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html

… ending with this blog by Jack Stewart

http://scn.sap.com/community/abap/blog/2013/08/28/dependency-injection-for-abap

I always thought the idea was great – often you have to create a bunch of objects and then “wire them together” by passing them into each other’s constructors so they know about each other.

This gives you the flexibility to pass in subclasses to alter the behaviour of the application – as I said I had first heard about this in the context of unit testing, but when I thought about it again naturally you can pass in any sort of subclass to change the way the program runs e.g. different subclass based on whatever criteria makes sense, just like the BADI filter mechanism.

That is a wonderful thing to be able to do, and subclassing is one of the few benefits of OO programming that one of my colleagues can get his head around, but it does tend to involve a lot of “boiler plate” programming i.e. lots of CREATE OBJECT statements, passing in assorted parameters.

Many Small Classes, make Light Work

http://scn.sap.com/community/abap/blog/2013/08/22/the-issue-with-having-many-small-classes

The idea is that the smaller and more focused your classes are, the easier they are to re-use and maintain. An OO principle is that a class should only have one reason to change i.e. it should do one thing only. If you follow that principle you get loads of benefits, but you have to create loads of classes in your program.

When I first started playing around with OO programming I was too lazy to keep writing CREATE OBJECT so I made everything static. That is not actually a sensible thing to do just to avoid work, as then you can’t subclass things. SAP itself found that out when they initially made ABAP proxy classes static.

The NEW Objects on the Block

In the Java programming language you create objects by saying GOAT = NEW GOAT as opposed to CREATE OBJECT GOAT.

In the “Head First Design Patterns Book” it gives a bunch of about five rules of programming which every Java programmer should aspire to but are in fact impossible to follow in real life.

One of those revolved around the rule being never to use the NEW statement because that hard coded the exact type of class you were creating, but how can you create objects if the only way to create them is to use the NEW statement?

In both Java and ABAP interfaces come into play here, you declare the ANIMAL object as an interface of type FARM ANIMAL (which GOAT implements) and say CREATE OBJECT ANIMAL TYPE GOAT. Perhaps a better example is in ABAP2XLS when you declare the object that writes out the file as an interface and then create it using the TYPE of the EXCEL version you want e.g. 2007.

Now you are always going to have to say the specific type (subclass) you want somewhere, but is it possible to decouple this from the exact instant you call the CREATE OBJECT statement?

Since you can have a dynamic CREATE OBJECT statement, you would think so, but how does this apparent diversion link back to what I was talking about earlier?

Jack Black and his blog Silver

Going back to Dependency Injection the blog by Jack Stewart contained a link to download some sample code. I downloaded it, had a look, thought it was great, and then totally re-wrote it. That is no reflection on the quality of the original; I am just physically incapable of not rewriting every single thing I come across.

I am going to include a SAPLINK file in text format at the end of this blog, but first I shall go through the code, top down. Firstly, this test program shows exactly what I am trying to achieve i.e. the same thing in less lines of code.

I have created some dummy Y classes which just have constructors to pass in a mixture of object instances and elementary data object parameters, my dear Watson. They only have one method each, just to write out if they are a base class or a subclass. The important thing is the effort involved to create them.

The Da Vinci Code Samples

First of all, a basic structure to get some elementary parameters and say if we want to use a test double or not. I am sticking with the unit test concept for now, but as I mentioned, you can pass in any old subclass you want, according to the good old, every popular, Liskov Substitution principle.

*&———————————————————————*
*& Report  Y_INJECTION_TEST
*&
*&———————————————————————*
* Show two ways to create linked objects, one using dependency injection
*——————————————————————–*
REPORT  y_injection_test.

PARAMETERS : p_valid TYPE sydatum,
             p_werks
TYPE werks_d,
             p_test 
AS CHECKBOX.

INITIALIZATION.
  p_valid
= sydatum.
  p_werks
= ‘3116’.

START-OF-SELECTION.
 
PERFORM do_it_the_long_way.
 
PERFORM do_it_the_short_way.

It’s a Long Long Way, from there to here

Firstly, the traditional way….

*&———————————————————————*
*&      Form  DO_IT_THE_LONG_WAY
*&———————————————————————*
* Normal way of doing things
*———————————————————————-*
FORM do_it_the_long_way .
 
DATA: lo_logger        TYPE REF TO ycl_test_logger.
 
DATA: lo_db_layer      TYPE REF TO ycl_test_db_layer.
 
DATA: lo_mock_db_layer TYPE REF TO ycl_test_mock_db_layer.
 
DATA: lo_simulator     TYPE REF TO ycl_test_simulator.

  CREATE OBJECT lo_logger.

  IF p_test = abap_true.

    CREATE OBJECT lo_mock_db_layer
     
EXPORTING
        io_logger  
= lo_logger
        id_valid_on
= p_valid.

    CREATE OBJECT lo_simulator
     
EXPORTING
        id_plant_id  
= p_werks
        io_db_layer  
= lo_mock_db_layer
        io_logger    
= lo_logger.

  ELSE.

    CREATE OBJECT lo_db_layer
     
EXPORTING
        io_logger  
= lo_logger
        id_valid_on
= p_valid.

    CREATE OBJECT lo_simulator
     
EXPORTING
        id_plant_id  
= p_werks
        io_db_layer  
= lo_db_layer
        io_logger    
= lo_logger.

  ENDIF.

  lo_simulator->say_who_you_are( ).

  SKIP.

ENDFORM.                    ” DO_IT_THE_LONG_WAY

Get Shorty

Now we do the same thing, using a Z class I created to use dependency injection.

*&———————————————————————*
*&      Form  DO_IT_THE_SHORT_WAY
*&———————————————————————*
*  Using Constructor Injection
*———————————————————————-*
FORM do_it_the_short_way .
* Local Variables
 
DATA: lo_simulator  TYPE REF TO ycl_test_simulator.

  zcl_bc_injector=>during_construction( :
    for_parameter
= ‘ID_PLANT_ID’ use_value = p_werks ),
    for_parameter
= ‘ID_VALID_ON’ use_value = p_valid ).

  IF p_test = abap_true.
   
“We want to use a test double for the database object
    zcl_bc_injector
=>instead_of( using_main_class = ‘YCL_TEST_DB_LAYER’
                                 use_sub_class   
= ‘YCL_TEST_MOCK_DB_LAYER’ ).
 
ENDIF.

  zcl_bc_injector=>create_via_injection( CHANGING co_object = lo_simulator ).

  lo_simulator->say_who_you_are( ).

ENDFORM.                    ” DO_IT_THE_SHORT_WAY

I think the advantage is self-evident – the second way is much shorter, and it’s got Big Feet.

If the importing parameter of the objectconstructor was an interface it would not matter at all. You just pass the interface name in to the INSTEAD_OF method as opposed to the main class name.

I have done virtually no error handling in the below code, except throwing fatal exceptions when unexpected things occur. This could be a lot more elegant, I am just demonstrating the basic principle.

Firstly the DURING CONSTRUCTION method analyses elementary parameters and then does nothing more fancy than adding entries to an internal table.

* Local Variables
 
DATA: lo_description       TYPE REF TO cl_abap_typedescr,
        ld_dummy            
TYPE string ##needed,
        ld_data_element_name
TYPE string,
        ls_parameter_values 
LIKE LINE OF mt_parameter_values.

  ls_parameter_valuesidentifier = for_parameter.

  CREATE DATA ls_parameter_valuesdo_value LIKE use_value.
 
GET REFERENCE OF use_value INTO ls_parameter_valuesdo_value.

  CHECK sysubrc = 0.

  CALL METHOD cl_abap_structdescr=>describe_by_data_ref
   
EXPORTING
      p_data_ref          
= ls_parameter_valuesdo_value
    RECEIVING
      p_descr_ref         
= lo_description
   
EXCEPTIONS
      reference_is_initial
= 1
     
OTHERS               = 2.

  IF sysubrc <> 0.
   
RETURN.
 
ENDIF.

  SPLIT lo_description->absolute_name AT ‘=’ INTO ld_dummy ld_data_element_name.

  ls_parameter_valuesrollname = ld_data_element_name.

  INSERT ls_parameter_values INTO TABLE mt_parameter_values.

It’s the same deal with the INSTEAD_OF method for saying what exact subclass you want to create, except it’s even simpler this time.

METHOD instead_of.
* Local Variables
 
DATA: ls_sub_classes_to_use LIKE LINE OF mt_sub_classes_to_use.

  ls_sub_classes_to_usemain_class = using_main_class.
  ls_sub_classes_to_use
sub_class  = use_sub_class.

  “Add entry at the start, so it takes priority over previous
 
“similar entries
 
INSERT ls_sub_classes_to_use INTO mt_sub_classes_to_use INDEX 1.

ENDMETHOD.

Now we come to the main CREATE_BY_INJECTION method.  I like to think I have written this as close to plain English as I can, so that this is more or less elf-explanatory.

METHOD create_via_injection.
* Local Variables
 
DATA: lo_class_in_reference_details  TYPE REF TO cl_abap_refdescr,
        lo_class_in_type_details      
TYPE REF TO cl_abap_typedescr,
        lo_class_to_create_type_detail
TYPE REF TO cl_abap_typedescr,
        ld_class_passed_in            
TYPE seoclassclsname,
        ld_class_type_to_create       
TYPE seoclassclsname,
        ls_created_objects            
LIKE LINE OF mt_created_objects,
        lt_signature_values           
TYPE abap_parmbind_tab.

* Determine the class type of the reference object that was passed in
  lo_class_in_reference_details ?= cl_abap_refdescr
=>describe_by_data( co_object ).
  lo_class_in_type_details      
= lo_class_in_reference_details->get_referenced_type( ).
  ld_class_passed_in            
= lo_class_in_type_details->get_relative_name( ).

  “See if we need to create the real class, or a subclass
  determine_class_to_create
(
   
EXPORTING
      id_class_passed_in            
= ld_class_passed_in
      io_class_in_type_details      
= lo_class_in_type_details
   
IMPORTING
      ed_class_type_to_create       
= ld_class_type_to_create
      eo_class_to_create_type_detail
= lo_class_to_create_type_detail ).

  READ TABLE mt_created_objects INTO ls_created_objects WITH TABLE KEY clsname = ld_class_type_to_create.

  IF sysubrc = 0.
   
“We already have an instance of this class we can use
    co_object ?= ls_created_objects
object.
   
RETURN.
 
ENDIF.

  “See if the object we want to create has parameters, and if so, fill them up
  fill_constructor_parameters
( EXPORTING io_class_to_create_type_detail = lo_class_to_create_type_detail
                              
IMPORTING et_signature_values            = lt_signature_values ).


  create_parameter_object
( EXPORTING id_class_type_to_create = ld_class_type_to_create
                                     it_signature_values    
= lt_signature_values       ” Parameter Values
                          
CHANGING  co_object               = co_object ).              ” Created Object

ENDMETHOD.

There is not a lot of point in drilling into this any further – I would encourage you to download the SAPLINK file, and then run this in debug mode to see what is happening.

In summary, I am always on the lookout for ways to reduce the so called “boiler plate” code, so the remaining code can concentrate on what the application is supposed to be doing as opposed to how it is doing it. This dependency injection business seems ideally suited so this purpose.

Now, while I am here.

/wp-content/uploads/2014/03/image002_418056.png

Did I mention I am giving a speech at the “Mastering SAP Technology 2014” conference at Melbourne on the 31/03/2014 – it’s about unit testing of ABAP programs.

What’s that? I’ve already mentioned this? Many times?

Oh dear, that must have slipped my mind. In that case I won’t go on about it, and I’ll sign off.

Cheersy Cheers

Paul

To report this post you need to login first.

4 Comments

You must be Logged on to comment or reply to a post.

  1. Fabio Pagoti

    Hi Paul!

    It’s impossible to comment on this blog post without mentioning mockA.

    https://github.com/uweku/mockA

    This is a mock framework created by Uwe Kunath to “mock” (aka instantiate) objects for you. In other words, a way of avoiding the “CREATE OBJECT” statement.

    It works a little bit different than your zcl_bc_injector class by the concept it pretty much the same: abstract class instantiation to use them inside other classes (dependecy injection).

    Taking this opportunity as a chance, project ABAP Workbench Objects Framework is using mockA quite a lot on its unit tests. And trust me, it saves tons of lines of code.

    https://github.com/fabiopagoti/WB-Objects

    By the way, WB Objects is another example of a framework with many classes and are small and can be reused. SAPLink itself is an application that could benefit from it. If you check SAPLink code, the logic to create XMLs (called Nuggs) is mixed with the selection logic of workbench objects details: classes doing more than one stuff.

    WB Objects abstracts workbench objects (many of them already including global and local classes and interfaces). It’s possible to use it to for example, find of subclasses given a parent class.

    Thanks for bringing such an important topic in the community again.

    Cheers,

    (0) 
    1. Paul Hardy Post author

      Hello

      If you drill into that blog referenced right at the top of the blog, you will see the blog is all about dependency injection, and one of the first comments is from me asking if he had ever heard of MOCKA.

      Then the next few comments tell me that DI is not just about unit testing and directed me to a few more blogs (listed above also). After reading them I thought just to get the concept clear in my head I would have a look at creating (modifying as it turned out) a tool to inject both mock objects for testing in development and real objects for varying program behavior in production.

      I am going to be looking at MOCKA a lot this year however, and I will also have a look at your WB_OBJECTS framework. Since code exchange went away it seems a bit more difficult to get an easy overview of all the assorted projects going on by different people / teams. If there IS an easy way to get such an overview of assorted live community projects can you point me at it?

      Cheersy Cheers

      Paul

      (0) 
      1. Fabio Pagoti

        Heu Paul!

        Yes, definitely dependency injection is a major capability OO in gereral and not only when doing unit testing. I am reading Heads First OO Analysis and Design which is really good as well. There are more examples over there.

        Talking about how to find frameworks or other projects… well.. I was not a huge fan of Code Exchange and IMHO there is no competitor for GitHub nowadays when it comes to open source repositories.

        So, I always take a look on GitHub to see what’s going on.

        Cheers,

        (0) 

Leave a Reply