Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member
8,069

Arguably one of the most misleading field values in the entire ABAP Workbench is this one:

It suggests that the constructor is just another instance method that is just called automatically by the kernel whenever an instance is created - but this is fundamentally wrong. Let me show you a small example for what can go wrong if you happen to trigger this trapdoor.

(Note that I'm using local classes here to cut down on the screenshots, but of course the same applies to global classes as well.)

Let's create a class that performs some fancy stuff during its initialization. Because we're good citizens and want to keep things reusable, we'll encapsulate the fancy stuff in its own method and call it from the constructor.

CLASS lcl_super DEFINITION.
  PUBLIC SECTION.
    METHODS constructor.
  PROTECTED SECTION.
    METHODS initialize_me.
ENDCLASS.                  

CLASS lcl_super IMPLEMENTATION.

  METHOD constructor.
    WRITE: / 'entering LCL_SUPER CONSTRUCTOR'.
    initialize_me( ).
    WRITE: / 'leaving LCL_SUPER CONSTRUCTOR'.
  ENDMETHOD.                   

  METHOD initialize_me.
    WRITE: / 'executing fancy stuff in LCL_SUPER INITIALIZE_ME'.
  ENDMETHOD.               

ENDCLASS.                  

Creating an instance of lcl_super yields a rather unspectacular output:

entering LCL_SUPER CONSTRUCTOR
executing fancy stuff in LCL_SUPER INITIALIZE_ME
leaving LCL_SUPER CONSTRUCTOR

Now let's add a subclass with its own constructor:

CLASS lcl_sub DEFINITION INHERITING FROM lcl_super.
  PUBLIC SECTION.
    METHODS constructor.
ENDCLASS.          

CLASS lcl_sub IMPLEMENTATION.

  METHOD constructor.
    WRITE: / 'entering LCL_SUB CONSTRUCTOR'.
    super->constructor( ).
    WRITE: / 'leaving LCL_SUB CONSTRUCTOR'.
  ENDMETHOD.            

ENDCLASS.       

The results aren't very surprising either:

entering LCL_SUB CONSTRUCTOR
entering LCL_SUPER CONSTRUCTOR
executing fancy stuff in LCL_SUPER INITIALIZE_ME
leaving LCL_SUPER CONSTRUCTOR
leaving LCL_SUB CONSTRUCTOR

Now someone might think that the fancy stuff in initialize_me isn't fancy enough or needs a different flavor of fancyness - whatever, since it's a protected method, we can just go ahead and redefine it. Still being the good citizens that we started off as, we ensure that the inherited implementation is called from the redefinition:

CLASS lcl_sub DEFINITION INHERITING FROM lcl_super.
  PUBLIC SECTION.
    METHODS constructor.
  PROTECTED SECTION.
    METHODS initialize_me REDEFINITION.
ENDCLASS.                   

CLASS lcl_sub IMPLEMENTATION.

  METHOD constructor.
    WRITE: / 'entering LCL_SUB CONSTRUCTOR'.
    super->constructor( ).
    WRITE: / 'leaving LCL_SUB CONSTRUCTOR'.
  ENDMETHOD.                  

  METHOD initialize_me.
    WRITE: / 'entering LCL_SUB INITIALIZE_ME'.
    WRITE: / 'performing more fancy stuff'.
    super->initialize_me( ).
    WRITE: / 'setting fancyness level to 11'.
    WRITE: / 'leaving LCL_SUB INITIALIZE_ME'.
  ENDMETHOD.                 

ENDCLASS.        

Looking good? Okay then, let's give it a try:

entering LCL_SUB CONSTRUCTOR
entering LCL_SUPER CONSTRUCTOR
executing fancy stuff in LCL_SUPER INITIALIZE_ME
leaving LCL_SUPER CONSTRUCTOR
leaving LCL_SUB CONSTRUCTOR

What the ...?

Okay, back to the drawing board. What went wrong here? A quick look into the documentation reveals this nugget:

In a constructor method, the methods of the subclasses of the class are not visible. If an instance constructor calls an instance method of the same class using the implicit self-reference me->, the method is called as it is implemented in the class of the instance constructor, and not in any redefined form that may occur in the subclass you want to instantiate. This is an exception to the rule that states that when you call instance methods, the system always calls the method as it is implemented in the class to whose instance the reference is pointing.

The rationale behind this is that the constructor is there to ensure that an instance of a class is initialized correctly and completely before any other operation is attempted. Calling a redefined method would circumvent this principle - the system would execute a method of the subclass although the construction of that class was not yet completed.

The irony of this problem is that the compiler will even tell you exactly this if you try to call an abstract protected method from within a constructor:

However, it won't be able to prevent the issue we've encountered above. It's a single-pass compiler that doesn't know about the subclasses that redefine the protected methods when compiling the superclass, and it doesn't know about the superclass calling redefined methods from within the constructor when compiling the subclasses. The compiler would also have to follow the entire call graph to ensure that no redefined method is called by a non-redefined method that is called by the constructor, and since the exact static call graph is undecidable, that just won't happen.

So what are our options? The most basic to prevent this from happening is to make the method private. If the subclasses still need to be able to call the method, make it final - this will at least prevent subclasses from overriding it. And think about whether you really need to call this method from the constructor - in most cases, there's a different way of structuring the code that will avoid this problem altogether.

32 Comments
dustyplanet
Active Participant
0 Kudos
This is by far, one of the most interesting articles on ABAP I have read, which drew me into writing this comment, something I haven't done for years...

I have always found it challenging to explain these eccentricities to developers coming from a "pure"(if there ever was one) Object Oriented background, but what's ABAP or ABAP Objects without the idiosyncrasies?

lukemarson
Active Contributor
0 Kudos
Thanks for taking the time to contribute this fine article to SDN. I foudn the topic quite interesting and think this information will be most useful to a number of ABAPers out there.

Well done and keep up the good work!
BrianVanderwiel
Participant
0 Kudos
Good blog, and perfect timing.  I "designed in" this mistake in to my first OO framework a while back and I just noticed it a couple weeks ago.  I had made comments telling the developer/user to "redefine as necessary"...luckily no one needed to.  I have since switched the methods to private and updated the comments.
  I especially appreciate the fact that this blog is concise and includes an example that can be easily copied/tested - nice work.
BrianVanderwiel
Participant
0 Kudos
Good blog, and perfect timing.  I "designed in" this mistake in to my first OO framework a while back and I just noticed it a couple weeks ago.  I had made comments telling the developer/user to "redefine as necessary"...luckily no one needed to.  I have since switched the methods to private and updated the comments.
  I especially appreciate the fact that this blog is concise and includes an example that can be easily copied/tested - nice work.
Peter_Inotai
Active Contributor
0 Kudos
Hi Volker,

Thanks for this interesting blog.

One of my colleague  also showed me something strange and I don’t understand why it’s possible.
You can use the attribute of an object, which is still initial as importing parameter during object creation. Normally you would get a short dump, that the object is initial, but it seems during object creation it’s possible.
Example:
Class CL_GUI_ALV_GRID CONSTRUCTOR

*... (6)create variant object
  if m_cl_variant is initial.
    create object m_cl_variant
           exporting
             it_outtab             = mt_outtab
             it_fieldcatalog       = m_cl_variant->mt_fieldcatalog
             it_sort               = m_cl_variant->mt_sort
             it_filter             = m_cl_variant->mt_filter
          it_grouplevels_filter = m_cl_variant->mt_grouplevels_filter
             is_variant            = m_cl_variant->ms_variant
             i_variant_save        = m_cl_variant->m_variant_save
             i_variant_default     = m_cl_variant->m_variant_default
             is_total_options      = m_cl_variant->ms_total_options
             is_layout             = m_cl_variant->ms_layout
             is_print              = m_cl_variant->ms_print
             i_www_active          = m_www
             i_cl_alv_grid         = me.

Any idea about this why it's possible?
Thanks,
Peter
alejandro_bindi
Active Contributor
0 Kudos
Luckily I never came across this trapdoor so far (I did get the "cannot call abstract methods in CONSTRUCTOR" error message), but this is great to know.

The best way to achieve something similar is to use some kind of factory method and setting the class as CREATE PRIVATE. So, instead of putting the call to the (redefined) initialize_me method in the constructor itself, you have this method:

METHOD factory.
  CREATE OBJECT rr_instance.
  rr_instance->initialize_me( ).
ENDMETHOD.

As a client of the class, you do not perform a direct CREATE OBJECT, but get a new instance via the FACTORY method.

(if you know i.s.h.med, you already know this concept with the CL_ISHMED* classes and their CREATE / LOAD and COMPLETE_CONSTRUCTION methods)

Regards
Former Member
0 Kudos
Great post. I'm always grateful when people write about the deep, technical guts of ABAP -- that kind of analysis is in such short supply.

This is an effect we see in almost all OOP languages -- C++ and Java have the same behaviour as ABAP. (I've never seen the Java compiler or an IDE warn you about it either, even though it has a multipass compiler and the most advanced IDEs around.) C# _does_ call the overridden virtual function of the subtype, even though the instance is not fully constructed (which has its own caveats and drawbacks).

I guess the philosophical debate as to which path to choose is only a non-issue in languages like SmallTalk that eschew constructors altogether. ("Real" OOP languages, as Alan Kay would tell you.) The "different way of structuring the code" that you mention is the only way in those languages.
Former Member
0 Kudos
Thank you for your kind words. As you said, this is a largely philosophical decision.  I definitely enjoy discussing the merits of selected programming languages over a decent $beverage. But sadly, during working hours, there isn't much time for philosophy and most of the decisions have already been made. So you either know about this issue or learn it the hard way...
Former Member
0 Kudos
Alejandro,

you're right about the factory method, but it has its own drawbacks because you can't redefine static methods. Since you always have to call the factory method of a specific class, redefining the initialize_me class alone won't help you, you also have to copy the factory method.

In IS-H / i.s.h.med, factory methods and even factory classes are used for another reason - depending on which license is installed, different classes are instantiated...

  Volker
Former Member
0 Kudos
Peter,

thank you for your comment. I will take a look at this and probably mention this in one of the next articles.

  Volker
Former Member
0 Kudos
Brian,

it's almost embarassing to say, but I've already run into this thing twice. I hope that publishing an article about it will make it finally stick in my memory...

  Volker
Former Member
0 Kudos
But any basic class/book of OOPS will teach you not to call a method inside a constructor .

Constructor methods are just for assignment purposes to the attributes that the class holds
Former Member
0 Kudos
But that basic class/book, like a lot of simplifications that try to provide you with a hard-and-fast set of rules like that, would be wrong.

First, there's nothing controversial about calling static methods in constructors (though, admittedly, that's probably not what you meant). More pertinently, if two or more of the constructors and methods of your class share common functionality (imagine a "reset()" method, that might contain constructor-like functionality), it can be extremely useful to define that in properly encapsulated instance methods. (ABAP doesn't support multiple instance constructors in a single class, but your comment referred to OOP in general.)

More generally, it's not true that constructor methods are just for assignment purposes -- while it's convenient, there are actually (maybe surprisingly) people who would argue against using them like that. Constructors are for setting up/initializing objects, which can often be more complex than member variable-setting. In light of that, it's also conceivable that the behaviour of a constructor might be sufficiently complex to benefit from decomposition into several methods.
SuhaSaha
Product and Topic Expert
Product and Topic Expert
0 Kudos
May i know which "Basic book" you're referring to? Neither SAP documentation states this nor is this defined as "OOP" principle.
Peter_Inotai
Active Contributor
0 Kudos
Cool.
I'm looking forward to your future blogs.
Peter
Former Member
0 Kudos
I added several ME-> and comments, to make it more testable.


CLASS lcl_super DEFINITION.
  PUBLIC SECTION.
    METHODS constructor.
  PROTECTED SECTION.
    METHODS initialize_me.
ENDCLASS.                  

CLASS lcl_super IMPLEMENTATION.

  METHOD constructor.
    WRITE: / 'entering LCL_SUPER CONSTRUCTOR'.
    initialize_me( ).
    me->initialize_me( ).
*the two lines above do the same job
*this me-> is explained by SAP help: 
*http://help.sap.com/saphelp_nw70/helpdata/en/dd
*/4049c40f4611d3b9380000e8353423/frameset.htm
*In a constructor method, the methods of the *subclasses of the class are not visible.
*If an instance constructor calls an instance *method of the same class using the
*implicit self-reference me->, the method is *called as it is implemented in the class
*of the instance constructor, and not in any *redefined form that may occur in the
*subclass you want to instantiate. This is an *exception to the rule that states
*that when you call instance methods, the system *always calls the method as it is
*implemented in the class to whose instance the *reference is pointing.   

    WRITE: / 'leaving LCL_SUPER CONSTRUCTOR'.
  ENDMETHOD.                   

  METHOD initialize_me.
    WRITE: / 'executing fancy stuff in LCL_SUPER INITIALIZE_ME'.
  ENDMETHOD.               

ENDCLASS.                  

CLASS lcl_sub DEFINITION INHERITING FROM lcl_super.
  PUBLIC SECTION.
    METHODS constructor.
  PROTECTED SECTION.
    METHODS initialize_me REDEFINITION.
ENDCLASS.                   

CLASS lcl_sub IMPLEMENTATION.

  METHOD constructor.
    WRITE: / 'entering LCL_SUB CONSTRUCTOR'.
*   initialize_me( ).
*   me->initialize_me( ).
*if you remove * from the two lines above
*system sill give an error and say
*in the constructor method, you can only access *instance attributes,instance methods, or "ME"
*after calling the constructor of the
*superclass (SUPER->CONSTRUCTOR).

    super->constructor( ).
    me->initialize_me( ).
*thie me-> points to the instance of class *lcl_sub
 
    WRITE: / 'leaving LCL_SUB CONSTRUCTOR'.
  ENDMETHOD.                  

  METHOD initialize_me.
    WRITE: / 'entering LCL_SUB INITIALIZE_ME'.
    WRITE: / 'performing more fancy stuff'.
    super->initialize_me( ).
    WRITE: / 'setting fancyness level to 11'.
    WRITE: / 'leaving LCL_SUB INITIALIZE_ME'.
  ENDMETHOD.                 

ENDCLASS.        

DATA instance TYPE REF TO LCL_SUB.

START-OF-SELECTION.

CREATE OBJECT instance.
Former Member
0 Kudos
Tony,

I'm not sure I get your point. From what I can see, the comments are partially misleading - for example, the error message you designated as "silly" is simply the consequence of having to initialize the superclass completely before performing any action on the subclass. Did I miss something essential here?

  Volker
Former Member
0 Kudos
Arshad,

I'd assume that these "basic books" would be about "Real" OOP languages designed by Alan Kay, as JAmes Geddes has already pointed out. There are many examples where a lot more than just simple attribute initialization has to happen in order to initialize an instance, and I can see absolutely no harm in splitting the constructor into several methods. It might even be advisable to do this to re-use code. Let's say, for example, you want to ensure that an attribute foo always has a valid value, and that checking the value is a non-trivial task. You could encapsulate the checking into a method that throws an exception in case of an error and call this method both from the constructor as well as the setter method. You just have to ensure that this method is either private or final to prevent the kind of issue I described in the article.
 
  Volker
Former Member
0 Kudos
Nice blog. Good to learn about these limitations. I am afraid I may have to review many classes because I am sure I have implemented this trap-door over and over. An instance not calling an overridden method of the subclass is a painful limitation an I hope this is adressed in future releases.

I have seen similair quirky behaviors over time and most of these have been addressed, let's hope this is addressed as ABAP moves closer and closer to becoming a fully compliant OO language.

Christiaan
Abhijeet-K
Active Participant
0 Kudos
I appreciate the thought process, the clean explanation and suggested remedies. Good work.
Former Member
0 Kudos
Rightly said. Every developer should follow the principle of not invoking any methods(except final ones) in the constructor.
Former Member
0 Kudos
Hello Volker,
Thanks for sharing details about intricate mistake that can happen in coding. The solution and suggestion approach is good.

Regards,
Prathap
former_member182010
Active Participant
0 Kudos
Hello Volker,

I just read your blog and I found it very worthwhile.  It is something to keep me in my memory on future assignments.

Kind Regards,
Rae Ellen Woytowiez
Former Member
0 Kudos
Thank you all for your kind words. My next ABAP Trapdoor post is already on its way - in fact, it's stuck somewhere in the moderation queue for over a week now. Does anybody happen to know where I can turn to in this case?
Former Member
0 Kudos
Christiaan,

I don't see this as a quirk - it's simply a design decision whether to resolve redefinitions during construction. I don't think that this will be changed - in fact, I sincerely hope it won't because that would cause a different kind of trouble altogether...

  Volker
alejandro_bindi
Active Contributor
0 Kudos
Volker, I get the problem you mention, I know of the limitation regarding static methods redefinition. It could be solved by parameterizing the factory method with the subtype you want an instance from (or making it determine the subclass indirectly depending on input parameters). Something like this:

CLASS lcl_super DEFINITION CREATE PRIVATE.
  PUBLIC SECTION.
    CLASS-METHODS:
      factory
        IMPORTING i_type              TYPE i
        RETURNING value(rr_instance)  TYPE REF TO lcl_super.
  PROTECTED SECTION.
    METHODS:
      initialize_me.
ENDCLASS.                    "lcl_super DEFINITION

CLASS lcl_super IMPLEMENTATION.
  METHOD factory.
    DATA: l_class TYPE seoclsname.

    CASE i_type.
      WHEN 1.
        l_class = 'LCL_SUB1'.
      WHEN OTHERS.
        l_class = 'LCL_SUPER'.
    ENDCASE.

    CREATE OBJECT rr_instance TYPE (l_class).
    rr_instance->initialize_me( ).
  ENDMETHOD.                    "factory

  METHOD initialize_me.
    WRITE: /'','Executing in SUPER...'.
  ENDMETHOD.                    "initialize_me
ENDCLASS.                    "lcl_super IMPLEMENTATION

CLASS lcl_sub1 DEFINITION INHERITING FROM lcl_super.
  PROTECTED SECTION.
    METHODS:
      initialize_me REDEFINITION.
ENDCLASS.                    "lcl_sub1 DEFINITION

CLASS lcl_sub1 IMPLEMENTATION.
  METHOD initialize_me.
    super->initialize_me( ).
    WRITE: /'','...and in SUB1'.
  ENDMETHOD.                    "initialize_me
ENDCLASS.                    "lcl_sub1 IMPLEMENTATION


DATA: gr_instance TYPE REF TO lcl_super.

START-OF-SELECTION.

  gr_instance = lcl_super=>factory( 0 ).
  ULINE.
  gr_instance = lcl_super=>factory( 1 ).
  ULINE.

(hope that was readable, unfortunately blog comments do not support the {code} tag I think, so indentation is lost)

However, I'd like to know what alternative you meant in your blog by "different way of structuring the code".

Looking forward also for your next trapdoor post!

Regards
Former Member
0 Kudos
Alejandro,

yes, that's a great way to bypass this issue, but it only works if your factory statically knows about all of the subtypes it has to create. In a classical framework scenario where the factory developer does not know about any subclasses that the users of the framework might develop, this is not the case.

The next obvious alternative would be to pass the class name as a parameter. This looks intriguing, but it has (at least) the drawback that it's up to the factory to ensure that the class name passed is a valid class name and the class is a subclass of the superclass. That's definitely possible, but not trivial and rather time-consuming. Either this or you accept the short dumps that will occur if someone tells your factory to create an instance of CL_GUI_FOOLBAR (typo intended ;-)).

If you really need this kind of flexibility, I usually use the Enhancement Framework to decouple the framework classes from the implementations. I usually supply a BAdI definition that can have multiple active implementations and whose implementations act as factories.

  Volker
Former Member
0 Kudos
Hi Volker,

Apologies for the literary fopa, more like a quirky design decision then. My point is that a framework or language should not limit bad practice, a developer should. In this case the language should allow you to call overridden methods from the constructor that is how inheritance works
To prevent a subclass calling or overriding these methods, the developer should make them final or private. But you should have the choice....

Anyway still good to know it is there and one should avoid any design decision where a feature such as this is required.

Christiaan
alejandro_bindi
Active Contributor
0 Kudos
You are right about the coupling of the superclass to the subclasses, even a change in the constructor of one of the known subclasses (e.g. changing signature) would result in adjustments having to be made in the superclass (or getting a nice dump).

Even though there seems to be no definitive solution, I was just trying to think of reasonable alternatives (which have their own drawbacks so far). If anyone comes up with others, keep them coming!
MarcelloUrbani
Active Contributor
0 Kudos
What I did in one of my projects is defining an interface with a static method with the same inputs of the constructor,that returns a boolean to tell the factory if the class wants to be instantiated.
All the subclasses will include said interface, and the factory reads the list of the subclasses from the database.
I like better the idea of a virtual method and an empty constructor, plus an internal table with a dummy instance of each class only good for said method (no need for the regular initialization) and a second method that returns a priority.But that's even more complicated.
naimesh_patel
Active Contributor
0 Kudos

Hello Volker,

Another option could be not use the CONSTRUCTOR when you need type of fancy design. Instead of that create a separate method which you need to call as soon as you instantiate the object. Here the execution doesn't have to understand the path of progress as its very well established by Object instantiation.

IMHO, it shouldn't be categorized as Trapdoor. Its basic design flaw as you are using constructor for different purpose.

Regards,

Naimesh Patel

Former Member
0 Kudos

Naimesh,

this is no fancy design. A constructor is used to initialize an object. If that initialization is more complex than the average "g_foo = i_foo" - for whatever reason - then there's no reason not to call methods from within the controller, especially if these methods already exist for other purposes. If they do, then there's a good chance that they are protected or even public and thus can be overriden - with the results I described above. This is no "different purpose", it's just a possible trap that you need to be aware of.

  Volker

Labels in this area