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

Uso de herencia

Objective

After completing this lesson, you will be able to utilice la herencia.

Up-Cast y Down-Cast

Para instanciar una subclase, declare una variable de referencia con el tipo de la clase relevante. A continuación, utilice el operador NEW para crear la instancia. Debe transferir valores a todos los parámetros obligatorios del constructor.

Uso de referencias de clase superior

Una variable de referencia con el tipo de una clase superior (en este caso lcl_plane) también puede contener referencias a objetos con el tipo de una subclase. Esto tiene sentido, ya que un avión de pasajeros o un avión de carga es un avión.

Sin embargo, una variable de referencia con el tipo de la clase superior solo conoce los componentes definidos en la clase superior. Por consiguiente, cuando se utiliza una referencia con este tipo para gestionar una instancia de una subclase, solo puede hacer referencia a los componentes de la clase superior, incluso si el objeto real al que hace referencia es una instancia de la subclase.

La asignación de referencias de objeto a una variable de referencia de un tipo diferente se denomina casting. La asignación de una referencia que señale a una variable de referencia con el tipo de una clase superior se conoce como upcast, porque está realizando la asignación a una clase superior de la jerarquía de herencia.

Llamar métodos después de un up cast

Cuando utiliza una variable de referencia con el tipo ref to lcl_plane para gestionar una instancia de avión de pasajeros, no puede utilizarla para acceder a componentes que pertenecen a la clase de avión de pasajeros, ya que la clase de avión no sabe que existen y la verificación de sintaxis no puede saber a qué tipo de objeto apuntará realmente la referencia en tiempo de ejecución.

Sin embargo, mediante la referencia, puede acceder a los métodos cuya definición original está incluida en la clase. Éste es el caso del método get_attributes en el ejemplo de la figura. La pregunta aquí es qué implementación del método se llamará. En este caso, el sistema busca el tiempo de ejecución para ver a qué tipo de objeto señala la variable de referencia y llama a la implementación desde la clase correspondiente. En otras palabras, aunque estamos utilizando una referencia de avión para gestionar una instancia de la clase de avión de pasajero, sigue siendo la implementación de método de la clase de avión de pasajero lo que se llama.

Asignar una referencia genérica a un tipo específico

Siempre puede asignar una variable de referencia con el tipo de una subclase a una variable de referencia con el tipo de una clase superior. Por ejemplo, siempre puede asignar una referencia de avión de pasajero a una referencia de avión. Como hemos visto, esto se debe a que un avión de pasajeros es un avión. Sin embargo, asignar variables de referencia en la dirección opuesta es más complicado. Esto se debe a que una referencia de avión puede contener referencias a otras clases distintas de la clase de avión de pasajero. En otras palabras, donde cada avión de pasajeros es un avión, no cada avión es un avión de pasajeros.

Dadas las referencias de objeto avión con tipo ref to lcl_plane y passer_plane con type ref to lcl_passer_plane, el sistema siempre le permitirá escribir avión = avión_pasajero. Sin embargo, la asignación aireo_pasajero = avión conduce a un error de sintaxis porque la verificación de sintaxis no puede estar suficientemente segura de que la referencia de avión contendrá realmente una referencia a un objeto de avión de pasajero en ese momento.

El operador CAST

El análisis sintáctico no permite asignar una referencia de clase superior directamente a una referencia de subclase. Sin embargo, aún puede hacerlo utilizando el operador CAST. Casting significa mirar un objeto como si tuviera un tipo diferente. En el ejemplo, le decimos al sistema que trate el "avión" como si fuera una instancia de la clase de avión de pasajeros. Esto es sintácticamente correcto, ya que ahora estamos fingiendo que el "plano" en el lado derecho de la expresión tiene el mismo tipo que "pasajero" en el lado izquierdo. Sin embargo, todavía no hay ninguna garantía de que el avión realmente contenga una referencia de avión de pasajeros, y si resulta que no contiene una en el tiempo de ejecución, la asignación no se puede realizar. Izquierda desprotegida, esta asignación podría provocar un error de tiempo de ejecución.

Proteger un downcast

Si no está seguro de que el objeto al que señala una variable de referencia es, en realidad, una instancia de la subclase correcta, puede comprobarlo utilizando la expresión lógica IS INSTANCE OF. En este ejemplo, la asignación a la referencia de avión de pasajeros solo se procesa si el avión realmente apunta a una instancia de la clase lcl_passer_plane.

Pruébelo: Up-Cast y Down-Cast

  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
    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
    CLASS lcl_plane DEFINITION. PUBLIC SECTION. TYPES: BEGIN OF ts_attributes, name TYPE string, value TYPE string, END OF ts_attributes, * declare table - do not allow the same attribute to be used more than once tt_attributes TYPE SORTED TABLE OF ts_attributes WITH UNIQUE KEY name. METHODS constructor IMPORTING iv_manufacturer TYPE string iv_type TYPE string. METHODS: get_Attributes RETURNING VALUE(rt_Attributes) TYPE tt_attributes. PROTECTED SECTION. DATA manufacturer TYPE string. DATA type TYPE string. PRIVATE SECTION. ENDCLASS. CLASS lcl_plane IMPLEMENTATION. METHOD constructor. manufacturer = iv_manufacturer. type = iv_type. ENDMETHOD. METHOD get_attributes. rt_attributes = VALUE #( ( name = 'MANUFACTURER' value = manufacturer ) ( name = 'TYPE' value = type ) ) . ENDMETHOD. ENDCLASS. CLASS lcl_cargo_plane DEFINITION INHERITING FROM lcl_plane. PUBLIC SECTION. METHODS constructor IMPORTING iv_manufacturer TYPE string iv_type TYPE string iv_cargo TYPE i. METHODS get_attributes REDEFINITION. private section. data cargo type i. ENDCLASS. CLASS lcl_cargo_plane IMPLEMENTATION. METHOD constructor. super->constructor( iv_manufacturer = iv_manufacturer iv_type = iv_type ). cargo = iv_cargo. ENDMETHOD. METHOD get_attributes. * method uses protected attributes of superclass rt_attributes = value #( ( name = 'MANUFACTURER' value = manufacturer ) ( name = 'TYPE' value = type ) ( name ='CARGO' value = cargo ) ). ENDMETHOD. ENDCLASS. CLASS lcl_passenger_plane DEFINITION INHERITING FROM lcl_Plane. PUBLIC SECTION. METHODS constructor IMPORTING iv_manufacturer TYPE string iv_type TYPE string iv_seats TYPE i. METHODS get_Attributes REDEFINITION. private section. data seats type i. ENDCLASS. CLASS lcl_passenger_plane IMPLEMENTATION. METHOD constructor. super->constructor( iv_manufacturer = iv_manufacturer iv_type = iv_type ). ENDMETHOD. METHOD get_attributes. * Redefinition uses call of superclass implementation rt_attributes = super->get_attributes( ). rt_Attributes = value #( base rt_attributes ( name = 'SEATS' value = seats ) ). 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
    12345678910111213141516171819202122232425262728293031323334353637383940414243444546
    DATA passenger TYPE REF TO lcl_passenger_Plane. DATA cargo TYPE REF TO lcl_cargo_plane. DATA plane TYPE REF TO lcl_Plane. passenger = NEW #( iv_manufacturer = 'BOEING' iv_type = '737-800' iv_seats = 130 ) . cargo = NEW #( iv_manufacturer = 'AIRBUS' iv_type = 'A340' iv_cargo = 60000 ). out->write( 'Output using passenger plane object reference' ). out->write( passenger->Get_attributes( ) ). plane = passenger. out->write( 'Output using superclass object reference' ). out->write( plane->get_attributes( ) ). * Can't use the superclass reference to get the number of seats of the * passenger plane, because the relevent method isn't declared in the superclass. * plane->get_seats( ). * Can't assign the plane directly to a passenger plane reference variable, because * not every plane is a passenger plane. *passenger = plane. * Make sure the plane is actually a passenger plane, then force the cast. IF plane IS INSTANCE OF lcl_passenger_plane. passenger = CAST #( plane ). ENDIF. ENDMETHOD.
  4. Seleccione CTRL + F3 para activar la clase y F9 para ejecutarla como una aplicación de consola.
  5. Juega con el código fuente para familiarizarte con los conceptos.

Trabajar con referencias de clase superior

En el ejercicio anterior, creó una clase superior común LCL_FLIGHT para las clases LCL_PASSENGER_FLIGHT y LCL_CARGO_FLIGHT. Ahora modificará la clase local LCL_CARRIER para utilizar la relación de herencia. Introducirá las variables de referencia utilizadas como parámetros para el tipo de la clase superior y, en lugar de dos atributos de instancia separados, pasajer_flights y cargo_flights, definirá un único atributo de instancia en el que puede almacenar referencias a todas las instancias de vuelo, independientemente de si son vuelos de pasajeros o de carga.

Modelo:

  • /LRN/CL_S4D401_OOS_INHERITANCE (clase global)

Solución:

  • /LRN/CL_S4D401_OOS_INH_USAGE (clase global)

Tarea 1: Copiar plantilla (opcional)

Copie la clase de modelo /LRN/CL_S4D401_OOS_INHERITANCE. 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_INHERITANCE 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_INHERITANCE 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: Parámetro genérico

En la clase local LCL_CARRIER, ajuste las definiciones de los métodos FIND_PASSENGER_FLIGHT y FIND_CARGO_FLIGHT. En ambos métodos, escriba el parámetro de exportación e_flight como una variable de referencia basada en la clase local LCL_FLIGHT.

Pasos

  1. En la clase local LCL_CARRIER, ajuste la definición del método FIND_CARGO_FLIGHT. Escriba el parámetro de retorno e_flight con LCL_FLIGHT en lugar de LCL_CARGO_FLIGHT. Asegúrese de guardar la modificación.

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

      ABAP
      123456789
      METHODS find_cargo_flight IMPORTING i_airport_from_id TYPE /dmo/airport_from_id i_airport_to_id TYPE /dmo/airport_to_id i_from_date TYPE /dmo/flight_date i_cargo TYPE /lrn/plane_actual_load EXPORTING e_flight TYPE REF TO lcl_flight e_days_later TYPE i.
  2. Analice la utilización del vuelo variable local en la implementación del método FIND_CARGO_FLIGHT.

    1. Para visualizar el tipo de vuelo de la variable de referencia, sitúe el cursor en el nombre de la variable y pulse F2.

  3. Analice la llamada del método FIND_CARGO_FLIGHT en el método IF_OO_ADT_CLASSRUN~MAIN en su clase global.

    1. Para visualizar el tipo de la variable de referencia cargo_flight, sitúe el cursor en el nombre de la variable y pulse F2.

  4. Ajuste la definición del método FIND_PASSENGER_FLIGHT de la misma manera. Escriba el parámetro de retorno e_flight con LCL_FLIGHT en lugar de LCL_PASSENGER_FLIGHT.

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

      ABAP
      123456789
      METHODS find_passenger_flight IMPORTING i_airport_from_id TYPE /dmo/airport_from_id i_airport_to_id TYPE /dmo/airport_to_id i_from_date TYPE /dmo/flight_date i_seats TYPE i EXPORTING e_flight TYPE REF TO lcl_flight e_days_later TYPE i.
  5. Active el código para verificar que esta modificación tampoco provoca errores de sintaxis.

    1. Pulse Ctrl + F3 para activar el código.

Tarea 3: Atributo genérico

En la clase local LCL_CARRIER, defina vuelos de atributo de instancia privados en los que pueda almacenar referencias a instancias de LCL_PASSENGER_FLIGHT y LCL_CARGO_FLIGHT al mismo tiempo. Rellene el atributo en la implementación del método CONSTRUCTOR con el contenido combinado de los atributos passer_flights y cargo_flights.

Pasos

  1. En la clase local LCL_FLIGHT, utilice una corrección rápida para generar un tipo de tabla pública que se base en esta clase (nombre sugerido para el tipo de tabla: tab).

    1. Vaya a la definición de la clase local LCL_FLIGHT.

    2. En la sentencia CLASS, sitúe el cursor en lcl_flight 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_flight.

    4. La nueva sentencia TYPES debería tener este aspecto:

      ABAP
      1
      TYPES tab TYPE STANDARD TABLE OF REF TO lcl_flight WITH DEFAULT KEY.
  2. En la sección privada de la clase local LCL_CARRIER, defina un nuevo atributo de instancia Vuelos. Escríbalo con el tipo de tabla que acaba de crear en la clase local LCL_FLIGHT.

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

      ABAP
      1234
      DATA passenger_flights TYPE lcl_passenger_flight=>tt_flights. DATA cargo_flights TYPE lcl_cargo_flight=>tt_flights. DATA flights TYPE lcl_flight=>tab.
  3. Ajuste la implementación del método CONSTRUCTOR de la clase local LCL_CARRIER. Después de llamar el método GET_FLIGHTS_BY_CARRIER de LCL_PASSENGER_FLIGHT, añada a los vuelos todas las referencias de objeto almacenadas en passer_flights.

    1. Vaya a la implementación del método CONSTRUCTOR en la clase local LCL_CARRIER.

    2. Antes de la sentencia ENDMETHOD, añada el siguiente código:

      ABAP
      123
      LOOP AT passenger_flights INTO DATA(passflight). APPEND passflight TO flights. ENDLOOP.

      Si prefiere una comprensión de tabla, añada el siguiente código en su lugar:

      ABAP
      12345
      flights = VALUE #( BASE flights FOR pflight IN passenger_flights ( pflight ) ).
  4. De manera similar, implemente un bucle sobre cargo_flights y añada estas referencias a los vuelos también.

    1. Antes de la sentencia ENDMETHOD, añada el siguiente código:

      ABAP
      123
      LOOP AT cargo_flights INTO DATA(cargoflight). APPEND cargoflight TO flights. ENDLOOP.

      Si prefiere una comprensión de tabla, añada el siguiente código en su lugar:

      ABAP
      12345
      flights = VALUE #( BASE flights FOR cflight IN cargo_flights ( cflight ) ).

Tarea 4: Acceso genérico y downcast

Elimine los atributos de instancia passer_flights y cargo_flights en la clase local LCL_CARRIER y sustitúyalos por los nuevos vuelos de atributo. Si el código accede a componentes específicos de las subclases, implemente un downcast.

Pasos

  1. En la parte de definición de la clase local LCL_CARRIER, elimine o comente la definición de los atributos passer_flights y cargo_flights.

    1. Seleccione las dos sentencias DATA y pulse Ctrl + < para añadir un signo de comentario delante de cada fila de código:

      ABAP
      12
      * DATA passenger_flights TYPE lcl_passenger_flight=>tt_flights. * DATA cargo_flights TYPE lcl_cargo_flight=>tt_flights.
  2. En el método CONSTRUCTOR, utilice declaraciones en línea para declarar airer_flights y cargo_flights como variables locales.

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

      ABAP
      1234567
      DATA(passenger_flights) = lcl_passenger_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ). DATA(cargo_flights) = lcl_cargo_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ).
  3. Después de que el método llame con las declaraciones en línea, rellene un atributo de instancia privado con el número de filas en la variable local passer_flights (nombre sugerido: pf_count).

    Consejo

    Implemente primero la valoración. A continuación, utilice una corrección rápida para crear la declaración de atributo.
    1. Ajuste el código de la siguiente manera:

      ABAP
      12345
      DATA(passenger_flights) = lcl_passenger_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ). pf_count = lines( passenger_flights ).
    2. Sitúe el cursor en pf_count, pulse Ctrl + 1 y seleccione Declarar atributo pf_count.

  4. De forma similar, rellene un atributo de instancia privado con el número de filas en la variable local cargo_flights (nombre sugerido: cf_count).

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

      ABAP
      12345
      DATA(cargo_flights) = lcl_cargo_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ). cf_count = lines( cargo_flights ).
    2. Sitúe el cursor en cf_count, pulse Ctrl + 1 y seleccione Declarar atributo cf_count.

  5. En el método GET_OUTPUT, sustituya las funciones LINES integradas por los atributos que acaba de definir.

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

      ABAP
      123456
      METHOD get_output. APPEND |{ 'Carrier Name:'(001) } { me->name }| TO r_result. APPEND |{ 'Passenger Flights:'(002) } { pf_count }| TO r_result. APPEND |{ 'Average free seats:'(003) } { get_average_free_seats( ) }| TO r_result. APPEND |{ 'Cargo Flights:'(004) } { cf_count }| TO r_result. ENDMETHOD.
  6. En el método FIND_CARGO_FLIGHT, sustituya el atributo cargo_flights por vuelos de atributo.

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

      ABAP
      12
      * LOOP AT me->cargo_flights INTO DATA(flight) LOOP AT me->flights INTO DATA(flight)
  7. En la llamada del método GET_FREE_CAPACITY añada una expresión CAST.

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

      ABAP
      1234
      IF connection_details-airport_from_id = i_airport_from_id AND connection_details-airport_to_id = i_airport_to_id * AND flight->get_free_capacity( ) >= i_cargo. AND CAST lcl_cargo_flight( flight )->get_free_capacity( ) >= i_cargo.
  8. Para asegurarse de que solo considera instancias de LCL_CARGO_FLIGHT, amplíe la condición WHERE del LOOP.

    Consejo

    Recuerde que en las tablas internas con tipo de fila elemental se dirige al contenido con pseudo-componente table_line.
    1. Ajuste el código de la siguiente manera:

      ABAP
      123
      LOOP AT me->flights INTO DATA(flight) WHERE table_line->flight_date >= i_from_date AND table_line IS INSTANCE OF lcl_cargo_flight.
  9. Ajuste la implementación del método FIND_PASSENGER_FLIGHT de la misma manera.

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

      ABAP
      12345678910111213141516171819
      LOOP AT me->flights INTO DATA(flight) WHERE table_line->flight_date >= i_from_date AND table_line IS INSTANCE OF lcl_passenger_flight. DATA(connection_details) = flight->get_connection_details( ). IF connection_details-airport_from_id = i_airport_from_id AND connection_details-airport_to_id = i_airport_to_id AND CAST lcl_passenger_flight( flight )->get_free_seats( ) >= i_seats. DATA(days_later) = flight->flight_date - i_from_date. IF days_later < e_days_later. "earlier than previous one? e_flight = flight. e_days_later = days_later. ENDIF. ENDIF. ENDLOOP.
  10. Ajuste la implementación del método GET_AVERAGE_FREE_SEATS. Utilice los vuelos de tabla internos en la reducción de tabla y añada una expresión CAST y una condición WHERE a la reducción. El número necesario de vuelos de pasajeros se almacena en el atributo pf_count creado anteriormente.

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

      ABAP
      12345678910111213141516
      METHOD get_average_free_seats. * Table Reduction ******************************************************** * r_result = REDUCE #( * INIT i = 0 * FOR flight IN passenger_flights * NEXT i += flight->get_free_seats( ) * ) / lines( passenger_flights ). r_result = REDUCE #( INIT i = 0 FOR flight IN flights WHERE ( table_line IS INSTANCE OF lcl_passenger_flight ) NEXT i += CAST lcl_passenger_flight( flight )->get_free_seats( ) ) / pf_count. ENDMETHOD.
  11. Active y pruebe su clase global como aplicación de consola.

    1. Pulse Ctrl + F3.

    2. Pulse F9.