Analyse et test du code
Utilisation correcte des types de données et des conversions de types
Traitement des zones de caractères
Utilisation du Pushdown de code dans ABAP SQL
Amélioration de la performance des tables internes
Implémentation des contrôles des autorisations
Conception d'un code orienté objet efficace
Définition et utilisation des classes d'exception
Ajout de documentation au code ABAP

Implémentation des méthodes Factory

Objective

After completing this lesson, you will be able to utilisez les méthodes Factory.

Méthodes Factory

Parfois, une classe doit garder le contrôle de ses instances. Cela peut empêcher la création de plusieurs instances. Pour ce faire, il doit empêcher ses utilisateurs de créer eux-mêmes des instances. Pour ce faire, utilisez l'option CREATE PRIVATE dans l'instruction CLASS DEFINITION dans ABAP. Toute tentative d'utilisation de l'opérateur NEW en dehors de la classe entraînera désormais une erreur de syntaxe. On notera que d'autres langages de programmation obtiennent cet effet en modifiant la visibilité de la méthode constructeur. ABAP ne prend pas cela en charge.

Étant donné que les utilisateurs de la classe ne peuvent pas créer leurs propres instances, la classe doit alors fournir une méthode statique publique qui crée des instances et les renvoie à l'utilisateur. C'est ce qu'on appelle une méthode Factory.

Essayer : implémentation de méthodes Factory

  1. Créez une nouvelle classe qui implémente l'interface IF_OO_ADT_CLASSRUN.
  2. Passez à l'onglet Types locaux et copiez l'extrait de code suivant dans l'éditeur :
    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. Passez à l'onglet Classe globale et collez l'extrait de code suivant dans l'implémentation de la méthode if_oo_adt_classrun~main( ) :
    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. Activez la classe avec Ctrl + F3.
  5. Jouez avec le codage. Déboguez en particulier la méthode main() et notez les numéros d'instance internes affectés aux objets renvoyés par la méthode Factory.

Modèle singleton

L'objectif du modèle singleton est de s'assurer qu'il n'existe qu'une seule instance d'une classe dans une application. Il peut s'agir d'une instance unique de la classe ou, dans notre cas, d'une instance unique pour chaque combinaison d'attributs clés.

Regardez les vidéos suivantes pour en savoir plus sur le modèle Singleton.

Essayer : implémentation de modèle Singleton

  1. Créez une nouvelle classe qui implémente l'interface IF_OO_ADT_CLASSRUN.
  2. Passez à l'onglet Types locaux et copiez l'extrait de code suivant dans l'éditeur :
    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. Passez à l'onglet Classe globale et collez l'extrait de code suivant dans l'implémentation de la méthode if_oo_adt_classrun~main( ) :
    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. Activez la classe avec Ctrl + F3.
  5. Jouez avec le codage. Déboguez en particulier la méthode main() et notez les numéros d'instance internes affectés aux objets renvoyés par la méthode Factory.

Implémenter une méthode Factory

Vous remarquez que votre code, dans sa forme actuelle, permet à plusieurs instances de LCL_CARRIER d'exister, représentant la même compagnie aérienne. Pour résoudre ce problème, vous allez définir et implémenter une méthode Factory qui garantit qu'une seule instance peut être créée pour une valeur d'ID de transporteur donnée.

Modèle :

  • /LRN/CL_S4D401_OOS_INTF_USAGE (classe globale)

Solution :

  • /LRN/CL_S4D401_OOS_FACTORY (classe globale)

Tâche 1: Copier modèle (facultatif)

Copiez la classe de modèle /LRN/CL_S4D401_OOS_INTF_USAGE. Si vous avez terminé l'exercice précédent, vous pouvez ignorer cette tâche et continuer à modifier votre classe ZCL_##_SOLUTION.

Étapes

  1. Copiez la classe /LRN/CL_S4D401_OOS_INTF_USAGE dans une classe de votre propre package (nom proposé : ZCL_##_SOLUTION, ## correspondant à votre numéro de groupe).

    1. Dans l'explorateur de projet, cliquez avec le bouton droit de la souris sur la classe /LRN/CL_S4D401_OOS_INTF_USAGE pour ouvrir le menu contextuel.

    2. Dans le menu contextuel, sélectionnez Dupliquer....

    3. Saisissez le nom de votre package dans la zone Package. Dans la zone Nom, saisissez ZCL_##_SOLUTION, où ## représente votre numéro de groupe.

    4. Cliquez sur Next.

    5. Confirmez l'ordre de transport et cliquez sur Terminer.

  2. Activez la copie.

    1. Appuyez sur Ctrl + F3 pour activer la classe.

Tâche 2: Définir une méthode Factory

Utilisez un correctif rapide pour définir une méthode Factory pour la classe locale LCL_CARRIER (nom suggéré : GET_INSTANCE). Dans la méthode Factory, exécutez le contrôle d'existence et le contrôle des autorisations avant de créer réellement une instance. Limitez ensuite la création d'instance pour cette classe afin d'appliquer l'utilisation de la méthode Factory.

Étapes

  1. Dans la classe locale LCL_CARRIER, utilisez un quickfix pour définir une méthode Factory.

    Astuce

    Les paramètres de constructeur sont ajoutés à la signature de la méthode Factory par défaut. Par conséquent, ne sélectionnez pas explicitement l'attribut carrier_id comme attribut à initialiser.
    1. Naviguez vers la définition de LCL_CARRIER.

    2. Dans l'instruction CLASS … DEFINITION, placez le curseur sur lcl_carrier et appuyez sur Ctrl + 1 pour appeler les solutions rapides disponibles.

    3. Dans la liste des solutions rapides disponibles, sélectionnez Générer méthode Factory Créer.

    4. Dans la zone Nom, saisissez GET_INSTANCE.

    5. Dans la section Sélectionner les attributs à initialiser, sélectionnez Désélectionner tout, puis Terminer.

    6. Le correctif rapide doit avoir ajouté la définition de méthode suivante :

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

      … et la mise en œuvre suivante :

      ABAP
      123
      METHOD get_instance. r_result = NEW #( i_carrier_id = i_carrier_id ). ENDMETHOD.
  2. Déplacez l'instruction SELECT et l'instruction AUTHORITY-CHECK de CONSTRUCTOR vers l'implémentation de la méthode Factory. Assurez-vous d'effectuer les contrôles avant l'instanciation.

    1. Naviguez vers l'implémentation de la méthode CONSTRUCTOR de la classe locale LCL_CARRIER.

    2. Sélectionnez l'instruction SELECT et l'instruction AUTHORITY-CHECK, y compris l'évaluation de la zone système SY-SUBRC et l'exception déclenchée, puis appuyez sur Ctrl + C pour copier l'extrait de code dans le presse-papiers.

    3. Appuyez sur Ctrl + < pour ajouter un signe de commentaire devant chaque ligne de code sélectionnée.

    4. Naviguez jusqu'à l'implémentation de la méthode GET_INSTANCE, placez le curseur avant la valorisation pour le paramètre r_result et appuyez sur Ctrl + V pour insérer l'extrait de code.

    5. Le code doit maintenant ressembler à ceci :

      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. Comme vous avez supprimé les instructions RAISE EXCEPTION de l'implémentation de la méthode CONSTRUCTOR, supprimez maintenant les deux ajouts RAISING correspondants dans la signature de la définition de méthode CONSTRUCTOR.

    1. Naviguez vers la définition de la méthode CONSTRUCTOR dans LCL_CARRIER.

    2. Adaptez la définition de méthode comme suit :

      ABAP
      123456
      METHODS constructor IMPORTING i_carrier_id TYPE /dmo/carrier_id * RAISING cx_abap_invalid_value * cx_abap_auth_check_exception .
  4. Revenez à l'implémentation de la méthode GET_INSTANCE. Dans l'instruction SELECT, ajustez la clause INTO. Stockez le résultat dans une structure déclarée en ligne (nom suggéré : détails) au lieu du nom des attributs d'instance et currency_code.

    1. Adaptez le code comme suit :

      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. Après l'instanciation de l'objet porteur, affectez les valeurs de la structure détaillée aux attributs d'instance correspondants de la nouvelle instance.

    1. Adaptez le code comme suit :

      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. Utilisez un correctif rapide pour ajouter des déclarations pour les deux exceptions.

    1. Cliquez sur l'icône d'avertissement avec l'ampoule à gauche de la première instruction RAISE EXCEPTION pour appeler les solutions rapides disponibles.

    2. Dans la liste des solutions rapides disponibles, sélectionnez Ajouter déclaration de retrait.

    3. Répétez cette opération pour l'autre instruction RAISE EXCEPTION.

    4. Le correctif rapide doit avoir ajusté le code comme suit :

      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. Limitez l'instanciation de la classe locale LCL_CARRIER. Assurez-vous que l'instanciation est autorisée uniquement dans la classe elle-même.

    1. Naviguez vers la définition de LCL_CARRIER.

    2. Adaptez le code comme suit :

      ABAP
      1
      CLASS lcl_carrier DEFINITION CREATE PRIVATE.
  8. Déplacez la définition de la méthode CONSTRUCTOR vers la section privée.

    Remarque

    Ceci est facultatif, mais devrait être fait pour améliorer la lisibilité.
    1. Copiez et collez l'instruction METHODS pour la méthode CONSTRUCTOR de la section publique vers la section privée.

  9. Dans la méthode IF_OO_ADT_CLASSRUN~MAIN de la classe globale, remplacez l'expression NEW par un appel de la méthode Factory GET_INSTANCE.

    1. Adaptez le code comme suit :

      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. Dans la méthode CLASS_SETUP de la classe de test locale LTCL_FIND_FLIGHTS, remplacez l'expression NEW par un appel de la méthode Factory GET_INSTANCE.

    1. Adaptez le code comme suit :

      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. Activez et testez votre classe globale comme application de console. Déboguez l'exécution de la méthode GET_INSTANCE.

    1. Appuyez sur Ctrl + F3.

    2. Appuyez sur F9.

Tâche 3: Étendre la méthode Factory

Dans la classe locale LCL_CARRIER, définissez un attribut statique dans lequel vous pouvez archiver des références aux instances de LCL_CARRIER (nom suggéré : instances). Étendez ensuite l'implémentation de la méthode Factory : après la création d'une instance, stockez la référence à la nouvelle instance dans les instances d'attributs. Créez une nouvelle instance uniquement si les instances ne font pas encore référence à un objet pour l'ID de transporteur demandé.

Étapes

  1. Dans la classe locale LCL_CARRIER, utilisez un quickfix pour créer un type de table pour cette classe (nom suggéré : tt_porteuses).

    1. Naviguez vers la définition de la classe locale LC_CARRIER.

    2. Dans l'instruction CLASS … DEFINITION, placez le curseur sur lcl_carrier et appuyez sur Ctrl + 1 pour appeler les solutions rapides disponibles.

    3. Dans la liste des solutions rapides disponibles, sélectionnez Générer type de table pour lcl_Carrier.

    4. Dans la nouvelle instruction TYPES, remplacez l'onglet par tt_porteuses.

    5. L'instruction TYPES doit maintenant ressembler à ceci :

      ABAP
      12
      TYPES: tt_carriers TYPE STANDARD TABLE OF REF TO lcl_carrier WITH DEFAULT KEY.
  2. Définissez un attribut statique privé basé sur le type de table que vous venez de créer (nom suggéré : instances).

    1. Faites défiler vers le bas jusqu'à l'instruction SECTION PRIVÉE.

    2. Adaptez le code comme suit :

      ABAP
      12
      PRIVATE SECTION. CLASS-DATA instances TYPE tt_carriers.
  3. Adaptez l'implémentation de la méthode Factory GET_INSTANCE. Après l'instanciation, ajoutez une référence à la nouvelle instance à l'attribut des instances.

    1. Adaptez le code comme suit :

      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. Avant l'instanciation, mais après les contrôles d'existence et d'autorisation, implémentez un enregistrement individuel lu à partir des instances d'attributs. Lisez l'instance dans laquelle la valeur de l'attribut carrier_id est égale à la valeur du paramètre d'import i_carrier_id. Enregistrez le résultat dans le paramètre de retour r_result.

    Astuce

    N'oubliez pas que dans les tables internes avec un type de ligne non structuré, vous adressez les données à l'aide du pseudo-composant table_line.
    1. Adaptez le code comme suit :

      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. Entourez l'accès en lecture à un enregistrement individuel, l'instanciation, les valorisations pour les attributs et l'instruction APPEND avec un TRY... ENDTRY structure de contrôle.

    1. Adaptez le code comme suit :

      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. Assurez-vous de créer une instance et de l'ajouter uniquement aux instances d'attributs si l'exception système CX_SY_ITAB_LINE_NOT_FOUND est déclenchée.

    1. Adaptez le code comme suit :

      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. Dans votre classe globale, ajustez l'implémentation de la méthode IF_OO_ADT_CLASSRUN~MAIN : implémentez un deuxième appel de la méthode Factory GET_INSTANCE avec la même entrée que l'appel existant, mais une variable de référence différente pour le résultat (nom proposé : carrier2).

    1. Adaptez le code comme suit :

      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. Activez et testez votre classe globale comme application de console. Déboguez le code pour vous assurer que les deux appels de la méthode Factory retournent la même instance de LCL_CARRIER.

    1. Appuyez sur Ctrl + F3.

    2. Appuyez sur F9.