Diesen Artikel habe ich ursprünglich im SAP Community Network. veröffentlicht.
Einer der wohl irreführendsten Feldwerte in der gesamten ABAP Workbench ist dieser:

Er suggeriert, dass der Konstruktor nur eine weitere Instanzmethode ist, die vom Kernel automatisch aufgerufen wird, sobald eine Instanz erstellt wird – aber das ist grundlegend falsch. Ich möchte anhand eines kleinen Beispiels zeigen, was schiefgehen kann, wenn man in diese Falle tappt.
(Ich verwende hier lokale Klassen, um die Anzahl der Screenshots zu reduzieren, aber dasselbe gilt natürlich auch für globale Klassen.)
Erstellen wir eine Klasse, die während ihrer Initialisierung einige ausgefallene Dinge ausführt. Da wir brave Entwickler sind und die Dinge wiederverwendbar halten wollen, kapseln wir die ausgefallenen Dinge in einer eigenen Methode und rufen sie vom Konstruktor aus auf.
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.
Die Erstellung einer Instanz von lcl_super
führt zu einer eher unspektakulären Ausgabe:
> entering LCL_SUPER CONSTRUCTOR
> executing fancy stuff in LCL_SUPER INITIALIZE_ME
> leaving LCL_SUPER CONSTRUCTOR
Fügen wir nun eine Unterklasse mit eigenem Konstruktor hinzu:
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.
Die Ergebnisse sind auch nicht sehr überraschend:
> entering LCL_SUB CONSTRUCTOR
> entering LCL_SUPER CONSTRUCTOR
> executing fancy stuff in LCL_SUPER INITIALIZE_ME
> leaving LCL_SUPER CONSTRUCTOR
> leaving LCL_SUB CONSTRUCTOR
Jetzt könnte man denken, dass die ausgefallenen Dinge in initialize_me
nicht ausgefallen genug sind oder eine andere
Art von Ausgefallenheit erfordern – was auch immer, da es sich um eine geschützte Methode handelt, können wir einfach
weitermachen und sie redefinieren. Da wir immer noch die braven Entwickler sind, als die wir angefangen haben, stellen
wir sicher, dass die geerbte Implementierung von der Neudefinition aufgerufen wird:
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.
Sieht gut aus? Okay, dann versuchen wir es mal:
> entering LCL_SUB CONSTRUCTOR
> entering LCL_SUPER CONSTRUCTOR
> executing fancy stuff in LCL_SUPER INITIALIZE_ME
> leaving LCL_SUPER CONSTRUCTOR
> leaving LCL_SUB CONSTRUCTOR
Was zum …?
Okay, zurück ans Zeichenbrett. Was ist hier schiefgelaufen? Ein kurzer Blick in die Dokumentation offenbart folgendes:
In Konstruktoren sind die Methoden von Unterklassen nicht sichtbar. Falls ein Instanzkonstruktor eine Instanzmethode der gleichen Klasse über die implizite Selbstreferenz
me->
aufruft, wird die Methode so aufgerufen, wie sie in der Klasse des Instanzkonstruktors implementiert ist und nicht die eventuell redefinierte Methode der zu instantiierenden Unterklasse. Dies ist eine Ausnahme von der Regel, dass beim Aufruf von Instanzmethoden immer die Implementierung in der Klasse aufgerufen wird, auf deren Instanz die Referenz gerade zeigt.
Der Grund dafür ist, dass der Konstruktor dafür sorgt, dass eine Instanz einer Klasse vor dem Versuch einer anderen Operation korrekt und vollständig initialisiert wird. Der Aufruf einer neu definierten Methode würde dieses Prinzip umgehen – das System würde eine Methode der Unterklasse ausführen, obwohl die Erstellung dieser Klasse noch nicht abgeschlossen war.
Die Ironie dieses Problems besteht darin, dass der Compiler uns genau dies mitteilt, wenn wir versuchen, eine abstrakte geschützte Methode aus einem Konstruktor heraus aufzurufen:

Allerdings kann er das oben beschriebene Problem nicht verhindern. Es handelt sich um einen Single-Pass-Compiler, der beim Kompilieren der Oberklasse nichts von den Unterklassen weiß, die die geschützten Methoden neu definieren, und er weiß auch nichts davon, dass die Oberklasse, die beim Kompilieren der Unterklassen neu definierte Methoden aus dem Konstruktor heraus aufruft. Der Compiler müsste auch dem gesamten Aufrufdiagramm folgen, um sicherzustellen, dass keine neu definierte Methode von einer nicht neu definierten Methode aufgerufen wird, die vom Konstruktor aufgerufen wird. Da das genaue statische Aufrufdiagramm nicht bestimmbar ist, wird dies einfach nicht passieren.
Welche Möglichkeiten haben wir also? Die grundlegendste Möglichkeit, dies zu verhindern, besteht darin, die Methode als privat zu definieren. Wenn die Unterklassen die Methode weiterhin aufrufen können müssen, machen Sie sie final – dies verhindert zumindest, dass Unterklassen sie überschreiben. Und überlegen Sie, ob Sie diese Methode wirklich vom Konstruktor aus aufrufen müssen – in den meisten Fällen gibt es eine andere Möglichkeit, den Code zu strukturieren, mit der sich dieses Problem vollständig vermeiden lässt.