Code analysieren und testen
Korrekte Verwendung von Datentypen und Typkonvertierungen
Verarbeitung von Zeichenfeldern
Code-Pushdown in ABAP SQL verwenden
Performance interner Tabellen verbessern
Implementieren von Berechtigungsprüfungen
Effektiven objektorientierten Code entwerfen
Ausnahmeklassen definieren und mit Ausnahmeklassen arbeiten
Dokumentation zum ABAP-Quelltext hinzufügen

Factory-Methoden implementieren

Objective

After completing this lesson, you will be able to verwenden Sie Factory-Methoden.

Factory-Methoden

Manchmal muss eine Klasse die Kontrolle über ihre Instanzen behalten. Dadurch kann verhindert werden, dass mehrere Instanzen angelegt werden. Dazu muss er verhindern, dass seine Benutzer selbst Instanzen anlegen. Dies geschieht in ABAP mit dem Zusatz CREATE PRIVATE in der Anweisung CLASS DEFINITION. Jeder Versuch, den Operator NEW außerhalb der Klasse zu verwenden, führt nun zu einem Syntaxfehler. Beachten Sie, dass andere Programmiersprachen diesen Effekt erzielen, indem sie die Sichtbarkeit der Konstruktormethode ändern. ABAP unterstützt dies nicht.

Da Verwender der Klasse keine eigenen Instanzen erzeugen können, muss die Klasse dann eine öffentliche statische Methode zur Verfügung stellen, die Instanzen erzeugt und an den Benutzer zurückgibt. Dies wird als Factory-Methode bezeichnet.

Try It Out: Implementierung von Factory-Methoden

  1. Legen Sie eine neue Klasse an, die das Interface IF_OO_ADT_CLASSRUN implementiert.
  2. Wechseln Sie zur Registerkarte Lokale Typen, und kopieren Sie den folgenden Code-Snippet in den Editor:
    Code Snippet
    12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
    CLASS lcl_connection DEFINITION CREATE PRIVATE. PUBLIC SECTION. METHODS constructor IMPORTING airlineid TYPE /dmo/carrier_id connectionnumber TYPE /dmo/connection_id fromAirport TYPE /dmo/airport_from_id toAirport TYPE /dmo/airport_to_id. CLASS-METHODS get_connection IMPORTING airlineId TYPE /dmo/carrier_id connectionNumber TYPE /dmo/connection_id RETURNING VALUE(ro_connection) TYPE REF TO lcl_Connection. PRIVATE SECTION. DATA AirlineId TYPE /dmo/carrier_id. DATA ConnectionNumber TYPE /dmo/connection_id. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. ENDCLASS. CLASS lcl_connection IMPLEMENTATION. METHOD constructor. me->airlineid = airlineid. me->connectionnumber = connectionnumber. me->fromAirport = fromAirport. me->toAirport = toAirport. ENDMETHOD. METHOD get_connection. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. SELECT SINGLE FROM /dmo/connection FIELDS airport_from_id, airport_to_id WHERE carrier_id = @airlineid AND connection_id = @connectionnumber INTO ( @fromAirport, @toAirport ). ro_connection = NEW #( airlineid = airlineid connectionnumber = connectionnumber fromairport = fromairport toairport = toairport ). ENDMETHOD. ENDCLASS.
  3. Wechseln Sie zur Registerkarte "Globale Klasse", und fügen Sie das folgende Code-Snippet in die Implementierung der Methode if_oo_adt_classrun~main( ) ein:
    Code Snippet
    12345678910111213141516171819202122232425262728293031323334
    CLASS zcl_s4d401_factory DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. interfaces if_oo_adt_classrun. PROTECTED SECTION. PRIVATE SECTION. ENDCLASS. CLASS zcl_s4d401_factory IMPLEMENTATION. METHOD if_oo_adt_classrun~main. data connection type ref to lcl_connection. * Debug the method to show that the class returns objects, but that there are different * objects for the same combination of airline and flight number connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). ENDMETHOD. ENDCLASS.
  4. Aktivieren Sie die Klasse mit Strg + F3.
  5. Spielen Sie mit dem Coding herum. Debuggen Sie insbesondere die Methode main() und notieren Sie sich die internen Instanznummern, die den von der Factory-Methode zurückgegebenen Objekten zugeordnet sind.

Singleton-Muster

Der Zweck des Singleton-Musters besteht darin, sicherzustellen, dass es nur eine Instanz einer Klasse in einer Anwendung gibt. Dabei kann es sich um eine einzelne Instanz der Klasse oder, in unserem Fall, um eine einzelne Instanz für jede Kombination von Schlüsselattributen handeln.

Sehen Sie sich die folgenden Videos an, um mehr über das Singleton-Muster zu erfahren.

Probieren Sie es aus: Singleton-Pattern-Implementierung

  1. Legen Sie eine neue Klasse an, die das Interface IF_OO_ADT_CLASSRUN implementiert.
  2. Wechseln Sie zur Registerkarte Lokale Typen, und kopieren Sie den folgenden Code-Snippet in den Editor:
    Code Snippet
    1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
    CLASS lcl_connection DEFINITION CREATE PRIVATE. PUBLIC SECTION. METHODS constructor IMPORTING airlineid TYPE /dmo/carrier_id connectionnumber TYPE /dmo/connection_id fromAirport TYPE /dmo/airport_from_id toAirport TYPE /dmo/airport_to_id. CLASS-METHODS get_connection IMPORTING airlineId TYPE /dmo/carrier_id connectionNumber TYPE /dmo/connection_id RETURNING VALUE(ro_connection) TYPE REF TO lcl_Connection. PRIVATE SECTION. TYPES: BEGIN OF ts_instance, airlineId TYPE /dmo/carrier_id, connectionNumber TYPE /dmo/connection_id, object TYPE REF TO lcl_connection, END OF ts_instance, tt_instances TYPE HASHED TABLE OF ts_instance WITH UNIQUE KEY airlineId ConnectionNumber. DATA AirlineId TYPE /dmo/carrier_id. DATA ConnectionNumber TYPE /dmo/connection_id. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. CLASS-DATA connections TYPE tt_instances. ENDCLASS. CLASS lcl_connection IMPLEMENTATION. METHOD constructor. me->airlineid = airlineid. me->connectionnumber = connectionnumber. me->fromAirport = fromAirport. me->toAirport = toAirport. ENDMETHOD. METHOD get_connection. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. IF NOT line_exists( connections[ airlineid = airlineid connectionnumber = connectionnumber ] ). SELECT SINGLE FROM /dmo/connection FIELDS airport_from_id, airport_to_id WHERE carrier_id = @airlineid AND connection_id = @connectionnumber INTO ( @fromAirport, @toAirport ). ro_connection = NEW #( airlineid = airlineid connectionnumber = connectionnumber fromairport = fromairport toairport = toairport ). DATA(new_instance) = VALUE ts_instance( airlineId = airlineId connectionnumber = connectionnumber object = ro_connection ). INSERT new_instance INTO TABLE connections. ELSE. ro_connection = connections[ airlineId = airlineId connectionnumber = connectionnumber ]-object. ENDIF. ENDMETHOD. ENDCLASS.
  3. Wechseln Sie zur Registerkarte "Globale Klasse", und fügen Sie das folgende Code-Snippet in die Implementierung der Methode if_oo_adt_classrun~main( ) ein:
    Code Snippet
    123456789101112131415161718192021222324252627282930313233
    CLASS zcl_s4d401_factory_singleton DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. interfaces if_oo_adt_classrun. PROTECTED SECTION. PRIVATE SECTION. ENDCLASS. CLASS zcl_s4d401_factory_singleton IMPLEMENTATION. METHOD if_oo_adt_classrun~main. data connection type ref to lcl_connection. * Debug the method to show that the class always returns the same object * for the same combination of airline and flight number connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). ENDMETHOD. ENDCLASS.
  4. Aktivieren Sie die Klasse mit Strg + F3.
  5. Spielen Sie mit dem Coding herum. Debuggen Sie insbesondere die Methode main() und notieren Sie sich die internen Instanznummern, die den von der Factory-Methode zurückgegebenen Objekten zugeordnet sind.

Factory-Methode implementieren

Sie stellen fest, dass Ihr Quelltext in seiner aktuellen Form mehrere Instanzen von LCL_CARRIER zulässt, die dieselbe Fluggesellschaft darstellen. Um dies zu beheben, definieren und implementieren Sie eine Factory-Methode, die sicherstellt, dass nur eine Instanz für einen bestimmten Spediteur-ID-Wert angelegt werden kann.

Vorlage:

  • /LRN/CL_S4D401_OOS_INTF_USAGE (globale Klasse)

Lösung:

  • /LRN/CL_S4D401_OOS_FACTORY (globale Klasse)

Aufgabe 1: Vorlage kopieren (optional)

Kopieren Sie die Vorlagenklasse /LRN/CL_S4D401_OOS_INTF_USAGE. Wenn Sie die vorherige Übung abgeschlossen haben, können Sie diese Aufgabe überspringen und mit der Bearbeitung Ihrer Klasse ZCL_##_SOLUTION fortfahren.

Schritte

  1. Kopieren Sie die Klasse /LRN/CL_S4D401_OOS_INTF_USAGE in eine Klasse in Ihrem eigenen Paket (Namensvorschlag: ZCL_##_SOLUTION, wobei ## für Ihre Gruppennummer steht).

    1. Klicken Sie im Project Explorer mit der rechten Maustaste auf die Klasse /LRN/CL_S4D401_OOS_INTF_USAGE, um das Kontextmenü zu öffnen.

    2. Wählen Sie im Kontextmenü Duplizieren....

    3. Geben Sie den Namen Ihres Pakets in das Feld Package ein. Geben Sie im Feld NameZCL_##_SOLUTION ein, wobei ## für Ihre Gruppennummer steht.

    4. Wählen Sie Next.

    5. Bestätigen Sie den Transportauftrag, und wählen Sie Finish.

  2. Aktivieren Sie die Kopie.

    1. Drücken Sie Strg + F3, um die Klasse zu aktivieren.

Aufgabe 2: Factory-Methode definieren

Verwenden Sie einen Quickfix, um eine Factory-Methode für die lokale Klasse LCL_CARRIER zu definieren (Namensvorschlag: GET_INSTANCE). Führen Sie in der Factory-Methode die Existenzprüfung und die Berechtigungsprüfung durch, bevor Sie tatsächlich eine Instanz anlegen. Schränken Sie anschließend die Instanzerzeugung für diese Klasse ein, um die Verwendung der Factory-Methode zu erzwingen.

Schritte

  1. Verwenden Sie in der lokalen Klasse LCL_CARRIER einen Quickfix, um eine Factory-Methode zu definieren.

    Hinweis

    Die Signatur der Factory-Methode wird standardmäßig um Konstruktorparameter erweitert. Wählen Sie daher das Attribut carrier_id nicht explizit als zu initialisierendes Attribut aus.
    1. Navigieren Sie zur Definition von LCL_CARRIER.

    2. Positionieren Sie in der Anweisung CLASS … DEFINITION den Cursor auf lcl_carrier, und drücken Sie Strg + 1, um die verfügbaren Quickfixes aufzurufen.

    3. Wählen Sie aus der Liste der verfügbaren Quickfixes Generieren Factory-Methode Anlegen.

    4. Geben Sie im Feld NameGET_INSTANCE ein.

    5. Wählen Sie im Abschnitt Zu initialisierende Attribute auswählen die Option Alle Markierungen aufheben und dann Fertigstellen.

    6. Das Quickfix sollte die folgende Methodendefinition hinzugefügt haben:

      ABAP
      12345
      CLASS-METHODS get_instance IMPORTING i_carrier_id TYPE /dmo/carrier_id RETURNING value(r_result) TYPE REF TO lcl_carrier.

      … und die folgende Implementierung:

      ABAP
      123
      METHOD get_instance. r_result = NEW #( i_carrier_id = i_carrier_id ). ENDMETHOD.
  2. Verschieben Sie die SELECT-Anweisung und die AUTHORITY-CHECK-Anweisung aus dem CONSTRUCTOR in die Implementierung der Factory-Methode. Stellen Sie sicher, dass Sie die Prüfungen vor der Instanziierung durchführen.

    1. Navigieren Sie zur Implementierung der Methode CONSTRUCTOR der lokalen Klasse LCL_CARRIER.

    2. Wählen Sie die SELECT-Anweisung und die AUTHORITY-CHECK-Anweisung aus, einschließlich der Auswertung des Systemfelds SY-SUBRC und der auslösenden Ausnahme, und drücken Sie Strg + C, um den Code-Snippet in die Zwischenablage zu kopieren.

    3. Drücken Sie Strg + <, um vor jeder ausgewählten Codezeile ein Kommentarzeichen hinzuzufügen.

    4. Navigieren Sie zur Implementierung der Methode GET_INSTANCE, positionieren Sie den Cursor vor der Wertzuweisung für den Parameter r_result, und drücken Sie Strg + V, um den Code-Snippet einzufügen.

    5. Der Quelltext sollte nun wie folgt aussehen:

      ABAP
      12345678910111213141516171819202122
      METHOD get_instance. SELECT SINGLE FROM /lrn/carrier FIELDS concat_with_space( carrier_id, name, 1 ), currency_code WHERE carrier_id = @i_carrier_id INTO ( @me->name, @me->currency_code ). IF sy-subrc <> 0. RAISE EXCEPTION TYPE cx_abap_invalid_value. ENDIF. AUTHORITY-CHECK OBJECT '/LRN/CARR' ID '/LRN/CARR' FIELD i_carrier_id ID 'ACTVT' FIELD '03'. IF sy-subrc <> 0. RAISE EXCEPTION TYPE cx_abap_auth_check_exception. ENDIF. r_result = NEW #( i_carrier_id = i_carrier_id ). ENDMETHOD.
  3. Da Sie die RAISE EXCEPTION-Anweisungen aus der Implementierung der Methode CONSTRUCTOR entfernt haben, löschen Sie nun die beiden entsprechenden RAISING-Zusätze in der Signatur der Methodendefinition CONSTRUCTOR.

    1. Navigieren Sie zur Definition der Methode CONSTRUCTOR in LCL_CARRIER.

    2. Passen Sie die Methodendefinition wie folgt an:

      ABAP
      123456
      METHODS constructor IMPORTING i_carrier_id TYPE /dmo/carrier_id * RAISING cx_abap_invalid_value * cx_abap_auth_check_exception .
  4. Kehren Sie zur Implementierung der Methode GET_INSTANCE zurück. Passen Sie in der SELECT-Anweisung die INTO-Klausel an. Speichern Sie das Ergebnis in einer inline deklarierten Struktur (vorgeschlagener Name: Details) anstelle der Instanzattribute name und currency_code.

    1. Passen Sie den Quelltext wie folgt an:

      ABAP
      1234567
      SELECT SINGLE FROM /lrn/carrier FIELDS concat_with_space( carrier_id, name, 1 ) AS name, currency_code WHERE carrier_id = @i_carrier_id * INTO ( @me->name, @me->currency_code ). INTO @DATA(details).
  5. Ordnen Sie nach der Instanziierung des Trägerobjekts die Werte aus der Detailstruktur den entsprechenden Instanzattributen der neuen Instanz zu.

    1. Passen Sie den Quelltext wie folgt an:

      ABAP
      12345
      r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. ENDMETHOD.
  6. Verwenden Sie eine Schnellkorrektur, um auslösende Deklarationen für die beiden Ausnahmen hinzuzufügen.

    1. Wählen Sie das Warnsymbol mit der Glühbirne auf der linken Seite der ersten RAISE EXCEPTION-Anweisung, um die verfügbaren Quickfixes aufzurufen.

    2. Wählen Sie in der Liste der verfügbaren Quickfixes Auslösungsdeklaration hinzufügen.

    3. Wiederholen Sie dies für die andere Anweisung RAISE EXCEPTION.

    4. Die Schnellkorrektur sollte den Quelltext wie folgt anpassen:

      ABAP
      12345678
      CLASS-METHODS get_instance IMPORTING i_carrier_id TYPE /dmo/carrier_id RETURNING value(r_result) TYPE REF TO lcl_carrier RAISING cx_abap_invalid_value cx_abap_auth_check_exception.
  7. Schränken Sie die Instanziierung der lokalen Klasse LCL_CARRIER ein. Stellen Sie sicher, dass die Instanziierung nur innerhalb der Klasse selbst erlaubt ist.

    1. Navigieren Sie zur Definition von LCL_CARRIER.

    2. Passen Sie den Quelltext wie folgt an:

      ABAP
      1
      CLASS lcl_carrier DEFINITION CREATE PRIVATE.
  8. Verschieben Sie die Definition der Methode CONSTRUCTOR in den privaten Bereich.

    Notiz

    Dies ist optional, sollte aber zur Verbesserung der Lesbarkeit erfolgen.
    1. Kopieren Sie die METHODS-Anweisung für die Methode CONSTRUCTOR aus dem öffentlichen Bereich, und fügen Sie sie in den privaten Bereich ein.

  9. Ersetzen Sie in der Methode IF_OO_ADT_CLASSRUN~MAIN der globalen Klasse den NEW -Ausdruck durch einen Aufruf der Factory-Methode GET_INSTANCE.

    1. Passen Sie den Quelltext wie folgt an:

      ABAP
      12
      * DATA(carrier) = NEW lcl_carrier( i_carrier_id = c_carrier_id ). DATA(carrier) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
  10. Ersetzen Sie in der Methode CLASS_SETUP der lokalen Testklasse LTCL_FIND_FLIGHTS den Ausdruck NEW durch einen Aufruf der Factory-Methode GET_INSTANCE.

    1. Passen Sie den Quelltext wie folgt an:

      ABAP
      12
      * the_carrier = NEW lcl_carrier( i_carrier_id = some_flight_data-carrier_id ). the_carrier = lcl_carrier=>get_instance( i_carrier_id = some_flight_data-carrier_id ).
  11. Aktivieren und testen Sie Ihre globale Klasse als Konsolen-App. Debuggen Sie die Ausführung der Methode GET_INSTANCE.

    1. Drücken Sie Strg + F3.

    2. Drücken Sie F9.

Aufgabe 3: Factory-Methode erweitern

Definieren Sie in der lokalen Klasse LCL_CARRIER ein statisches Attribut, in dem Referenzen auf die Instanzen von LCL_CARRIER abgelegt werden können (Namensvorschlag: Instanzen). Erweitern Sie anschließend die Implementierung der Factory-Methode: Speichern Sie nach dem Anlegen einer Instanz die Referenz auf die neue Instanz in Attributinstanzen. Legen Sie nur dann eine neue Instanz an, wenn Instanzen noch kein Objekt für die angeforderte Spediteur-ID referenzieren.

Schritte

  1. Verwenden Sie in der lokalen Klasse LCL_CARRIER einen Quickfix, um einen Tabellentyp für diese Klasse anzulegen (Namensvorschlag: tt_carrier).

    1. Navigieren Sie zur Definition der lokalen Klasse LC_CARRIER.

    2. Positionieren Sie in der Anweisung CLASS … DEFINITION den Cursor auf lcl_carrier, und drücken Sie Strg + 1, um die verfügbaren Quickfixes aufzurufen.

    3. Wählen Sie aus der Liste der verfügbaren Quickfixes die Option Tabellentyp für lcl_carrier generieren.

    4. Ersetzen Sie in der neuen TYPES -Anweisung tab durch tt_carrier.

    5. Die TYPES -Anweisung sollte nun wie folgt aussehen:

      ABAP
      12
      TYPES: tt_carriers TYPE STANDARD TABLE OF REF TO lcl_carrier WITH DEFAULT KEY.
  2. Definieren Sie ein privates statisches Attribut basierend auf dem soeben angelegten Tabellentyp (Namensvorschlag: Instanzen).

    1. Blättern Sie nach unten zur PRIVATE SECTION -Anweisung.

    2. Passen Sie den Quelltext wie folgt an:

      ABAP
      12
      PRIVATE SECTION. CLASS-DATA instances TYPE tt_carriers.
  3. Passen Sie die Implementierung der Factory-Methode GET_INSTANCE an. Fügen Sie nach der Instanziierung dem Instanzenattribut eine Referenz auf die neue Instanz hinzu.

    1. Passen Sie den Quelltext wie folgt an:

      ABAP
      123456
      r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. APPEND r_result TO instances. ENDMETHOD.
  4. Implementieren Sie vor der Instanziierung, aber nach den Existenz- und Berechtigungsprüfungen einen einzelnen Datensatz, der aus Attributinstanzen gelesen wird. Lesen Sie die Instanz, in der der Wert des Attributs carrier_id dem Wert des Importparameters i_carrier_id entspricht. Speichern Sie das Ergebnis im Rückgabeparameter r_result.

    Hinweis

    Beachten Sie, dass bei internen Tabellen mit nicht strukturiertem Zeilentyp die Daten über die Pseudokomponente table_line angesprochen werden.
    1. Passen Sie den Quelltext wie folgt an:

      ABAP
      1234567891011
      IF sy-subrc <> 0. RAISE EXCEPTION TYPE cx_abap_auth_check_exception. ENDIF. r_result = instances[ table_line->carrier_id = i_carrier_id ]. r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. APPEND r_result TO instances.
  5. Umschließen Sie den Einzelsatzlesezugriff, die Instanziierung, die Wertzuweisungen an die Attribute und die APPEND-Anweisung mit einem TRY... ENDTRY Kontrollstruktur.

    1. Passen Sie den Quelltext wie folgt an:

      ABAP
      123456789
      TRY. r_result = instances[ table_line->carrier_id = i_carrier_id ]. r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. APPEND r_result TO instances. ENDTRY.
  6. Stellen Sie sicher, dass Sie nur dann eine Instanz anlegen und diese zu Attributinstanzen hinzufügen, wenn die Systemausnahme CX_SY_ITAB_LINE_NOT_FOUND ausgelöst wird.

    1. Passen Sie den Quelltext wie folgt an:

      ABAP
      123456789
      TRY. r_result = instances[ table_line->carrier_id = i_carrier_id ]. CATCH cx_sy_itab_line_not_found. r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. APPEND r_result TO instances. ENDTRY.
  7. Passen Sie in Ihrer globalen Klasse die Implementierung der Methode IF_OO_ADT_CLASSRUN~MAIN an: Implementieren Sie einen zweiten Aufruf der Factory-Methode GET_INSTANCE mit derselben Eingabe wie der vorhandene Aufruf, aber einer anderen Referenzvariablen für das Ergebnis (Namensvorschlag: carrier2).

    1. Passen Sie den Quelltext wie folgt an:

      ABAP
      12
      DATA(carrier) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ). DATA(carrier2) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
  8. Aktivieren und testen Sie Ihre globale Klasse als Konsolen-App. Debuggen Sie den Quelltext, um sicherzustellen, dass die beiden Aufrufe der Factory-Methode dieselbe Instanz von LCL_CARRIER zurückgeben.

    1. Drücken Sie Strg + F3.

    2. Drücken Sie F9.