Das Singleton-Design Pattern in ABAP Objects

Der Singleton ist eines der bekanntesten Design Patterns (deutsch „Entwurfsmuster“) in der objektorientierten Programmierung. Und das geht auch in ABAP.

Singleton bedeutet, dass es von der betreffenden Klasse nur ein einziges Objekt geben kann. Es wird technisch sichergestellt, dass man kein zweites Objekt dieser Klasse erzeugen kann.

Wofür braucht man überhaupt ein Singleton?

Nun als erstes braucht man den Singleton als Übungsaufgabe für das objektorientierte Programmieren und zur Vorbereitung auf die ABAP-Zertifizierung. Aber man kann tatsächlich praktische Anwendungen dafür finden:

Wenn es in der objektorientierten Modellierung, wichtig ist, dass es von irgend etwas nur ein einziges Exemplar geben kann. z.B. ein Logfile, einen speziellen Server, einen Application Controller, einen Puffer, eine einzige Verwaltung von Customizingeinträgen, dann kann es sinnvoll sein, das auch durch den Code zu erzwingen.

Woher stammt das englische Wort Singleton?

Manche Begriffe kennt man als Nichtmuttersprachler im Englischen nur aus der Computerei. Viele Begriffe haben aber im Englischen einen anschaulichen Hintergrund und sie haben damit auch bildhafte Assoziationen, die uns dann im Deutschen bei diesen Begrifen fehlen.

Der Singleton war im Englischen schon vor der objektorientierten Programmierung bekannt. Er wird dort z.B. so verwendet:

  • im Kartenspiel: wenn man von einer Farbe nur diese eine Karte hat.
  • in der Mathematik: eine Menge mit nur einem einzigen enthaltenen Element
  • ein einzelnes Kind im Unterschied zu einem Zwilling oder Drilling

Wenn du im Deutschen Singleton hörst, dann denke einfach an ein Einzelstück.

Übrigens ist es im Deutschen der Singleton und nicht das Singleton. Jedenfalls ist nur das erstere richtig, auch wenn man das letztere ab und an hört.

Zentrale technische Aspekte des Singletons

In allen objektorientierten Programmiersprachen funktioniert der Singleton so:

  • Es wird sichergestellt, dass nur ein einziges Exemplar überhaupt erzeugt werden kann. Dazu gehört, dass der Konstruktor nicht allgemein verfügbar ist. Der Konstruktor muss also auf die Sichtbarkeitsebene „private“ gesetzt werden.
  • Es gibt eine öffentliche (public) Methode, die das einzige Exemplar der Klasse auf Anfrage herausgibt.
  • Für die Erzeugung dieses einzigen Exemplars gibt es zwei Möglichkeiten:
    • eager initialization: das Singleton wird so früh wie möglich erzeugt
    • lazy initlialization: das Singleton wird erst in dem Augenblick erzeugt, in dem das Objekt zum ersten Mal tatsächlich gebraucht wird.

Beides ist mit ABAP möglich, und zwar so:

Lazy initialization

Der übliche Fall bei Singletons ist die „lazy initialization“, die „faule Initialisierung“. Das Objekt wird erst dann erzeugt, wenn es gar nicht mehr anders geht, also dann, wenn der erste das Objekt tatächlich in die Hand nehmen will.

Die Methode get_instance schaut nach, ob das Objekt schon vorliegt. Falls ja, wird es einfach herausgegeben. Falls es noch nicht existiert, wird es kurzerhand erzeugt. Die Referenz auf dieses Exemplar wird dann aufbewahrt. Weitere Zugriffe auf die Instanz geben dann dasselbe Exemplar heraus.

REPORT z_lazy_init_singleton.

" beachten: create private !
CLASS lcl_lazy_init_singleton DEFINITION CREATE PRIVATE.

  PUBLIC SECTION.
    CLASS-METHODS: get_instance RETURNING value(r_instance) TYPE REF TO lcl_lazy_init_singleton.

    METHODS: do_something.
  PROTECTED SECTION.
  PRIVATE SECTION.
    " die eine und einzige Instanz der Klasse
    CLASS-DATA: instance TYPE REF TO lcl_lazy_init_singleton.
ENDCLASS.

CLASS lcl_lazy_init_singleton IMPLEMENTATION.
  METHOD get_instance.
    " Lazy initialization:
    " beim ersten Anfragen der Instanz erzeugen.
    IF instance IS NOT BOUND.
      CREATE OBJECT instance.
    ENDIF.
    r_instance = instance.
  ENDMETHOD.

  METHOD do_something.
    WRITE : / 'doing something'.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
  WRITE: / 'Lazy Initialization'.
  lcl_lazy_init_singleton=>get_instance( )->do_something( ).

Eager Initialization

Eager Initialization bedeutet, dass der Singleton schon im Vorwege erzeugt wird, dann wenn die Klasse geladen wird. So steht es dann, wenn es zum ersten Mal gebraucht wird, auf jeden Fall zur Verfügung. Ob das ein Vorteil oder ein Nachteil ist, hängt vom Einzelfall ab.

Ein Vorteil könnte es sein, dass ein zeitaufwändiges Erzeugen schon beim Programmstart erfolgt. Bei tatsächlichen Zugriff ist die Reaktionszeit dann für den Anwender immer schnell.

Nachteilig ist aber, dass der Singleton auf jeden Fall erzeugt wird, auch wenn er ggf. nie gebraucht wird.

Abap bietet jedenfalls technisch den Weg dazu, auch die eager initialization zu organisieren: statt den Singleton beim ersten Zugriff in get_instance zu erzeugen, wird jetzt ein statischer Klassenkonstruktor verwendet.

REPORT z_eager_init_singleton.

" beachten: create private !
CLASS lcl_eager_init_singleton DEFINITION CREATE PRIVATE.

  PUBLIC SECTION.
    constants: some_constant type string value 'some_value'.

    CLASS-METHODS: get_instance RETURNING value(r_instance) TYPE REF TO lcl_eager_init_singleton.
    class-METHODS: class_constructor.
    METHODS: do_something.
  PROTECTED SECTION.
  PRIVATE SECTION.
    " die eine und einzige Instanz der Klasse
    CLASS-DATA: instance TYPE REF TO lcl_eager_init_singleton.
ENDCLASS.

CLASS lcl_eager_init_singleton IMPLEMENTATION.

  method class_constructor.
     " eager initialization: Erzeugen des Objekts beim ersten Zugriff auf die Klasse
     write : 'Erzeuge Objekt'.
     create object instance.
  ENDMETHOD.

  METHOD get_instance.
    r_instance = instance.
  ENDMETHOD.

  METHOD do_something.
    WRITE : / 'doing something'.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
  WRITE: / 'Eager Initialization'.
  write: / 'Zugriff auf Konstante:', lcl_eager_init_singleton=>some_constant.
  WRITE: / 'Zugriff auf Instanz'.

  lcl_eager_init_singleton=>get_instance( )->do_something( ).

Achtung: der statische Klassenkonstruktor ist nicht dasselbe wie der normale Konstruktor für Objekte der Klasse. Den haben wir hier gar nicht definiert, weil es in ihm gar nichts zu tun gibt.

Es handelt sich stattdessen um den statischen Klassenkonstruktor, der beim Laden der Klasse einmalig durchlaufen wird. Dabei ist es egal, ob man eine Methode oder ein Attribut der Klasse aufruft. Es reicht, dass die Klasse überhaupt geladen wird.

Die Ausgabe dieses Programms lautet:

Beispielprogramm für ein Singleton mit Eager Initialization                                                                                                 -----------------------------------------------------------
Erzeuge Objekt
Eager Initialization
Zugriff auf Konstante: some_value
Zugriff auf Instanz
doing something

Das ist nun etwas überraschend, weil das Objekt nun noch früher erzeugt wird, als ich es eigentlich gedacht hatte. Allerdings haben wir es hier mit einer lokalen Klasse zu tun, die nur innerhalb des Reports existiert. Diese Klasse wird offenbar schon geladen, wenn der Report auch nur angefasst wird. Der Callstack zeigt, dass der Aufruf innerhalb unmittelbar nach start-of-selection geschieht und noch vor dem ersten Write-Befehl.

Eager Initialization mit globaler Klasse

Da drängt sich die Frage auf, ob die Ursache in der lokalen Klasse liegt, oder ob ich den class-constructor von ABAP falsch verstanden habe. Also noch ein Versuch. Hier ist das neue Hauptprogramm:

REPORT z_eager_init_singleton2.

start-of-selection.

  WRITE: / 'Eager Initialization'.
  write: / 'Zugriff auf Konstante:', zcl_eager_init_singleton=>some_constant.
  WRITE: / 'Zugriff auf Instanz'.

  zcl_eager_init_singleton=>get_instance( )->do_something( ).

Und hier ist der Singleton, diesmal als globale Klasse (über SE24):

CLASS zcl_eager_init_singleton DEFINITION
  PUBLIC
  FINAL
  CREATE PRIVATE .

  PUBLIC SECTION.
    CONSTANTS: some_constant TYPE string VALUE 'some_value'.

    CLASS-METHODS: get_instance RETURNING value(r_instance) TYPE REF TO zcl_eager_init_singleton.
    CLASS-METHODS: class_constructor.
    METHODS: do_something.

  PROTECTED SECTION.
  PRIVATE SECTION.
*   die eine und einzige Instanz der Klasse
    CLASS-DATA: instance TYPE REF TO zcl_eager_init_singleton.

    METHODS constructor .
ENDCLASS.

CLASS ZCL_EAGER_INIT_SINGLETON IMPLEMENTATION.
  METHOD class_constructor.
*   eager initialization: Erzeugen des Objekts beim ersten Zugriff auf die Klasse
    WRITE : 'Erzeuge Objekt'.
    CREATE OBJECT instance.
  ENDMETHOD.

  METHOD constructor.
  ENDMETHOD.

  METHOD do_something.
    WRITE : / 'doing something'.
  ENDMETHOD.

  METHOD get_instance.
    r_instance = instance.
  ENDMETHOD.
ENDCLASS.

Auch hier das gleiche Ergebnis:

Beispielprogramm für ein Singleton mit Eager Initialization                                                                                                 --------------------------------------------------
Erzeuge Objekt
Eager Initialization
Zugriff auf Konstante: some_value
Zugriff auf Instanz
doing something

Auch hier reicht es, dass die Klasse überhaupt im Programm verwendet wird, um den Klassenkonstruktor aufzurufen. Vollelektrisch als erstes in start-of-selection, vor jedem anderen Befehl. Das Verhalten ist also dasselbe wie in einer lokalen Klasse.

Übrigens geht mittlerweile in ABAP auch in SE24 die Beschränkung des Konstruktors auf die Freigabeebene private! (Das war in älteren ABAP-Versionen nicht der Fall.) Und das ist ganz so wie man es für das Singleton-Pattern braucht.

This article is also available in English.

Weiterführende Links

Bildquelle: herzlichen Dank an Devanath auf Pixabay

heiko

Dipl.-Ing. Heiko Evermann

Vorheriger Artikel