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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 |