Analizar y probar el código
Uso correcto de tipos de datos y conversiones de tipos
Campos de caracteres de procesamiento
Uso de la transferencia de código en ABAP SQL
Mejora del rendimiento de la tabla interna
Implementar verificaciones de autorización
Diseño de código orientado a objetos efectivo
Definir y trabajar con clases de excepción
Añadir documentación al código ABAP

Implementación de métodos factory

Objective

After completing this lesson, you will be able to utilice métodos factory.

Métodos factory

A veces, una clase necesita mantener el control de sus instancias. Esto podría ser para evitar que se creen varias instancias. Para ello, debe evitar que sus usuarios creen instancias por sí mismos. Esto se realiza en ABAP utilizando el suplemento CREATE PRIVATE en la sentencia CLASS DEFINITION. Cualquier intento de utilizar el operador NEW fuera de la clase ahora provocará un error de sintaxis. Tenga en cuenta que otros lenguajes de programación logran este efecto alterando la visibilidad del método constructor. ABAP no lo admite.

Dado que los usuarios de la clase no pueden crear sus propias instancias, la clase debe proporcionar un método estático público que cree instancias y las devuelva al usuario. Esto se denomina método factory.

Pruébelo: Implementación de métodos factory

  1. Cree una clase nueva que implemente la interfaz IF_OO_ADT_CLASSRUN.
  2. Cambie a la pestaña Tipos locales y copie el siguiente fragmento de código en el 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. Cambie a la pestaña Clase global y pegue el siguiente fragmento de código en la implementación del método 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. Active la clase con Ctrl + F3.
  5. Reproducir con la codificación. En particular, depure el método main() y anote los números de instancia internos asignados a los objetos devueltos por el método factory.

Patrón singleton

El objetivo del patrón singleton es garantizar que solo haya una instancia de una clase en una aplicación. Podría ser una única instancia de la clase o, en nuestro caso, una única instancia para cada combinación de atributos clave.

Vea los siguientes vídeos para obtener más información sobre el patrón Singleton.

Pruébelo: Implementación de patrón Singleton

  1. Cree una clase nueva que implemente la interfaz IF_OO_ADT_CLASSRUN.
  2. Cambie a la pestaña Tipos locales y copie el siguiente fragmento de código en el 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. Cambie a la pestaña Clase global y pegue el siguiente fragmento de código en la implementación del método 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. Active la clase con Ctrl + F3.
  5. Reproducir con la codificación. En particular, depure el método main() y anote los números de instancia internos asignados a los objetos devueltos por el método factory.

Implementar un método factory

Observa que su código, en su forma actual, permite que existan varias instancias de LCL_CARRIER, que representan a la misma compañía aérea. Para solucionarlo, definirá e implementará un método factory que garantice que solo se pueda crear una instancia para un valor de ID de transportista determinado.

Modelo:

  • /LRN/CL_S4D401_OOS_INTF_USAGE (clase global)

Solución:

  • /LRN/CL_S4D401_OOS_FACTORY (clase global)

Tarea 1: Copiar plantilla (opcional)

Copie la clase de modelo /LRN/CL_S4D401_OOS_INTF_USAGE. Si ha finalizado el ejercicio anterior, puede omitir esta tarea y continuar editando su clase ZCL_##_SOLUTION.

Pasos

  1. Copie la clase /LRN/CL_S4D401_OOS_INTF_USAGE en una clase de su propio paquete (nombre sugerido: ZCL_##_SOLUTION, donde ## representa su número de grupo).

    1. En el Explorador de proyectos, haga clic con el botón derecho en la clase /LRN/CL_S4D401_OOS_INTF_USAGE para abrir el menú contextual.

    2. En el menú contextual, seleccione Duplicar....

    3. Introduzca el nombre del paquete en el campo Paquete. En el campo Nombre, introduzca ZCL_##_SOLUTION, donde ## representa su número de grupo.

    4. Seleccione Siguiente.

    5. Confirme la orden de transporte y seleccione Finalizar.

  2. Active la copia.

    1. Pulse Ctrl + F3 para activar la clase.

Tarea 2: Definir un método factory

Utilice una corrección rápida para definir un método factory para la clase local LCL_CARRIER (nombre sugerido: GET_INSTANCE). En el método factory, realice la verificación de existencia y la verificación de autorización antes de crear realmente una instancia. A continuación, restrinja la creación de instancias para esta clase para forzar la utilización del método factory.

Pasos

  1. En la clase local LCL_CARRIER, utilice una corrección rápida para definir un método factory.

    Consejo

    Los parámetros del constructor se añaden a la firma del método factory de forma predeterminada. Por lo tanto, no seleccione explícitamente el atributo carrier_id como atributo a inicializar.
    1. Navegue a la definición de LCL_CARRIER.

    2. En la sentencia CLASS … DEFINITION, sitúe el cursor en lcl_carrier y pulse Ctrl + 1 para invocar las correcciones rápidas disponibles.

    3. De la lista de correcciones rápidas disponibles, seleccione Generar método factory crear.

    4. En el campo Nombre, introduzca GET_INSTANCE.

    5. En la sección Seleccionar atributos para inicializar, seleccione Desmarcar todo y, a continuación, Finalizar.

    6. La corrección rápida debería haber añadido la siguiente definición de método:

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

      … y la siguiente aplicación:

      ABAP
      123
      METHOD get_instance. r_result = NEW #( i_carrier_id = i_carrier_id ). ENDMETHOD.
  2. Mueva la sentencia SELECT y la sentencia AUTHORITY-CHECK del CONSTRUCTOR a la implementación del método factory. Asegúrese de realizar las verificaciones antes de la instanciación.

    1. Navegue hasta la implementación del método CONSTRUCTOR de la clase local LCL_CARRIER.

    2. Seleccione la sentencia SELECT y la sentencia AUTHORITY-CHECK, incluida la evaluación del campo de sistema SY-SUBRC y la emisión de excepciones, y pulse Ctrl + C para copiar el fragmento de código en el portapapeles.

    3. Pulse Ctrl + < para añadir un signo de comentario delante de cada fila de código seleccionada.

    4. Navegue a la implementación del método GET_INSTANCE, sitúe el cursor antes de la valoración para el parámetro r_result y pulse Ctrl + V para insertar el fragmento de código.

    5. El código ahora debería tener el siguiente aspecto:

      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. Dado que ha eliminado las sentencias RAISE EXCEPTION de la implementación del método CONSTRUCTOR, borre ahora los dos suplementos RAISING correspondientes en la firma de la definición del método CONSTRUCTOR.

    1. Navegue a la definición del método CONSTRUCTOR en LCL_CARRIER.

    2. Ajuste la definición del método de la siguiente manera:

      ABAP
      123456
      METHODS constructor IMPORTING i_carrier_id TYPE /dmo/carrier_id * RAISING cx_abap_invalid_value * cx_abap_auth_check_exception .
  4. Vuelva a la implementación del método GET_INSTANCE. En la sentencia SELECT, ajuste la cláusula INTO. Almacene el resultado en una estructura declarada en línea (nombre sugerido: detalles) en lugar del nombre de los atributos de instancia y currency_code.

    1. Ajuste el código de la siguiente manera:

      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. Después de la instanciación del objeto transportista, asigne los valores de la estructura de detalles a los atributos de instancia correspondientes de la nueva instancia.

    1. Ajuste el código de la siguiente manera:

      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. Utilice una corrección rápida para añadir declaraciones de emisión para las dos excepciones.

    1. Seleccione el icono de advertencia con la bombilla en la parte izquierda de la primera sentencia RAISE EXCEPTION para invocar las correcciones rápidas disponibles.

    2. En la lista de correcciones rápidas disponibles, seleccione Agregar declaración de emisión.

    3. Repita esto para la otra sentencia RAISE EXCEPTION.

    4. La corrección rápida debería haber ajustado el código de la siguiente manera:

      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. Restrinja la instanciación de la clase local LCL_CARRIER. Asegúrese de que la instanciación solo está permitida dentro de la propia clase.

    1. Navegue a la definición de LCL_CARRIER.

    2. Ajuste el código de la siguiente manera:

      ABAP
      1
      CLASS lcl_carrier DEFINITION CREATE PRIVATE.
  8. Mueva la definición del método CONSTRUCTOR a la sección privada.

    Nota

    Esto es opcional, pero debe hacerse para mejorar la legibilidad.
    1. Copie y pegue la sentencia METHODS para el método CONSTRUCTOR de la sección pública a la sección privada.

  9. En el método IF_OO_ADT_CLASSRUN~MAIN de la clase global, sustituya la expresión NEW por una llamada del método factory GET_INSTANCE.

    1. Ajuste el código de la siguiente manera:

      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. En el método CLASS_SETUP de la clase de test local LTCL_FIND_FLIGHTS, sustituya la expresión NEW por una llamada del método factory GET_INSTANCE.

    1. Ajuste el código de la siguiente manera:

      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. Active y pruebe su clase global como aplicación de consola. Depure la ejecución del método GET_INSTANCE.

    1. Pulse Ctrl + F3.

    2. Pulse F9.

Tarea 3: Ampliar el método factory

En la clase local LCL_CARRIER, defina un atributo estático en el que puede almacenar referencias a las instancias de LCL_CARRIER (nombre sugerido: instancias). A continuación, amplíe la implementación del método factory: Después de la creación de una instancia, almacene la referencia a la nueva instancia en instancias de atributo. Cree una instancia nueva solo si las instancias aún no hacen referencia a un objeto para el ID de transportista solicitado.

Pasos

  1. En la clase local LCL_CARRIER, utilice una corrección rápida para crear un tipo de tabla para esta clase (nombre sugerido: tt_carrier).

    1. Navegue a la definición de la clase local LC_CARRIER.

    2. En la sentencia CLASS … DEFINITION, sitúe el cursor en lcl_carrier y pulse Ctrl + 1 para invocar las correcciones rápidas disponibles.

    3. En la lista de correcciones rápidas disponibles, seleccione Generar tipo de tabla para lcl_carrier.

    4. En la nueva sentencia TYPES, sustituya tab por tt_carrier.

    5. La sentencia TYPES ahora debería tener el siguiente aspecto:

      ABAP
      12
      TYPES: tt_carriers TYPE STANDARD TABLE OF REF TO lcl_carrier WITH DEFAULT KEY.
  2. Defina un atributo estático privado basado en el tipo de tabla recién creado (nombre sugerido: instancias).

    1. Desplácese hacia abajo hasta la sentencia PRIVATE SECTION.

    2. Ajuste el código de la siguiente manera:

      ABAP
      12
      PRIVATE SECTION. CLASS-DATA instances TYPE tt_carriers.
  3. Ajuste la implementación del método factory GET_INSTANCE. Después de la instanciación, añada una referencia a la nueva instancia al atributo de instancias.

    1. Ajuste el código de la siguiente manera:

      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. Antes de la instanciación, pero después de las verificaciones de existencia y autorización, implemente un registro individual leído de instancias de atributo. Lea la instancia en la que el valor del atributo carrier_id es igual al valor del parámetro de importación i_carrier_id. Almacene el resultado en el parámetro de retorno r_result.

    Consejo

    Recuerde que en las tablas internas con tipo de fila no estructurado se abordan los datos mediante el pseudocomponente table_line.
    1. Ajuste el código de la siguiente manera:

      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. Rodea el acceso de lectura de registro individual, la instanciación, las asignaciones de valores a los atributos y la sentencia APPEND con un TRY... estructura de control ENDTRY.

    1. Ajuste el código de la siguiente manera:

      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. Asegúrese de crear solo una instancia y añádala a instancias de atributo si se genera la excepción de sistema CX_SY_ITAB_LINE_NOT_FOUND.

    1. Ajuste el código de la siguiente manera:

      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. En su clase global, ajuste la implementación del método IF_OO_ADT_CLASSRUN~MAIN: implemente una segunda llamada al método factory GET_INSTANCE con la misma entrada que la llamada existente, pero una variable de referencia diferente para el resultado (nombre sugerido: carrier2).

    1. Ajuste el código de la siguiente manera:

      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. Active y pruebe su clase global como aplicación de consola. Depure el código para asegurarse de que las dos llamadas del método factory devuelven la misma instancia de LCL_CARRIER.

    1. Pulse Ctrl + F3.

    2. Pulse F9.