Using Inheritance

Objective

After completing this lesson, you will be able to Use inheritence.

Up-Cast and Down-Cast

To instantiate a subclass, you declare a reference variable with the type of the relevant class. Then you use the NEW operator to create the instance. You must pass values to all obligatory parameters of the constructor.

Using Superclass References

A reference variable with the type of a superclass (in this case lcl_plane) can also hold references to objects with the type of a subclass. This makes sense, since a passenger plane or a cargo plane is a plane.

However, a reference variable with the type of the superclass is only aware of the components defined in the superclass. Consequently, when you use a reference with this type to manage an instance of a subclass, you can only address the components from the superclass, even though the actual object to which the reference points is an instance of the subclass.

Assigning object references to a reference variable of a different type is called casting. Assigning a reference that points to a subclass to a reference variable with the type of a superclass is called an up-cast, because you are making the assignment to a type higher up in the inheritance hierarchy.

Calling Methods After an Up-Cast

When you use a reference variable with type ref to lcl_plane to manage a passenger plane instance, you cannot use it to access components that belong to the passenger plane class, as the plane class does not know that they exist and the syntax check cannot know what type of object the reference will actually point to at runtime.

Using the reference, you can, however, access methods whose original definition is contained in the class. This is the case with the get_attributes method in the example in the figure. The question here is which implementation of the method will be called. In this case, the system looks at runtime to see what type of object the reference variable is pointing to, and calls the implementation from the corresponding class. In other words, although we are using a plane reference to manage an instance of the passenger plane class, it is still the method implementation from the passenger plane class that is called.

Assigning a Generic Reference to a Specific Type

You can always assign a reference variable with the type of a subclass to a reference variable with the type of a superclass. For example, you can always assign a passenger plane reference to a plane reference. As we have seen, this is because a passenger plane is a plane. Assigning reference variables in the opposite direction, however, is more tricky. This is because a plane reference can contain references to other classes than the passenger plane class. In other words, where every passenger plane is a plane, not every plane is a passenger plane.

Given an object references plane with type ref to lcl_plane and passenger_plane with type ref to lcl_passenger_plane, the system will always allow you to write plane = passenger_plane. However, the assignment passenger_plane = plane leads to a syntax error because the syntax check cannot be sufficiently sure that the plane reference will actually hold a reference to a passenger plane object at that moment.

The CAST Operator

The syntax check does not let you assign a superclass reference directly to a subclass reference. However, you can still do it using the CAST operator. Casting means looking at an object as though it has a different type. In the example, we tell the system to treat "plane" as though it were an instance of the passenger plane class. This is syntactically correct, as we are now pretending that "vehicle" on the right-hand side of the expression has the same type as "passenger" on the left-hand side. However, there is still no guarantee that plane actually contains a passenger plane reference, and if it turns out not to contain one at runtime, the assignment cannot be made. Left unprotected, this assignment could cause a runtime error.

Securing a Down-Cast

If you are not certain that the object to which a reference variable is pointing is actually an instance of the correct subclass, you can check it using the logical expression IS INSTANCE OF. In this example, the assignment to the passenger plane reference is only processed if plane actually points to an instance of the class lcl_passenger_plane.

Try It Out: Up-Cast and Down-Cast

  1. Create a new class that implements the interface IF_OO_ADT_CLASSRUN.
  2. Switch to the Local Types tab and copy the following code snippet into the editor:
    Code Snippet
    Copy code
    Switch to dark mode
    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. Switch to the Global Class tab and paste the following code snippet into the implementation of method if_oo_adt_classrun~main( ):
    Code Snippet
    Copy code
    Switch to dark mode
    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. Select CTRL + F3 to activate the class and F9 to execute it as a console app.
  5. Play around with the source code to get familiar with the concepts.

Work with Superclass References

You introduced a common superclass LCL_FLIGHT for classes LCL_PASSENGER_FLIGHT and LCL_CARGO_FLIGHT. Now you adjust local class LCL_CARRIER to make use of the inheritance relation. You type parameters with a superclass reference and instead of two separate instance attributes passenger_flights and cargo_flights, you define a single instance attribute flights in which you can store the references to all flight instances, whether they are passenger flights or cargo flights.

Template:

  • /LRN/CL_S4D401_OOS_INHERITANCE (Global Class)

Solution:

  • /LRN/CL_S4D401_OOS_INH_USAGE (Global Class)

Task 1: Copy Template (Optional)

Copy the template class. If you finished the previous exercise, you can skip this task and continue editing your class ZCL_##_SOLUTION.

Steps

  1. Copy class /LRN/CL_S4D401_OOS_INHERITANCE to a class in your own package (suggested name: ZCL_##_SOLUTION, where ## stands for your group number).

    1. In the Project Explorer view, right-click class /LRN/CL_S4D401_OOS_INHERITANCE to open the context menu.

    2. From the context menu, choose Duplicate ....

    3. Enter the name of your package in the Package field. In the Name field, enter the name ZCL_##_SOLUTION, where ## stands for your group number.

    4. Adjust the description and choose Next.

    5. Confirm the transport request and choose Finish.

  2. Activate the copy.

    1. Press Ctrl + F3 to activate the class.

Task 2: Generic Parameter

In local class LCL_CARRIER, adjust the definitions of methods FIND_PASSENGER_FLIGHT and FIND_CARGO_FLIGHT. In both methods, type the exporting parameter e_flight as a reference based on local class LCL_FLIGHT.

Steps

  1. In local class LCL_CARRIER, adjust the definition of method FIND_CARGO_FLIGHT. Type the returning parameter e_flight with LCL_FLIGHT instead of LCL_CARGO_FLIGHT.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345678910
      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_days_later TYPE i.
  2. Analyze the implementation of method FIND_CARGO_FLIGHT.

    1. To display the type of reference variable flight, place the cursor on the variable name and press F2.

  3. Analyze the call method FIND_CARGO_FLIGHT in method IF_OO_ADT_CLASSRUN~MAIN in your global class.

    1. To display the type of reference variable pass_flight, place the cursor on the variable name and press F2.

  4. Adjust the definition of method FIND_PASSENGER_FLIGHT in the same way. Type the returning parameter e_flight with LCL_FLIGHT instead of LCL_PASSENGER_FLIGHT.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345678910
      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_days_later TYPE i.
  5. Activate the code to verify that this change does also not cause any syntax errors.

    1. Press Ctrl + F3 to activate the code.

Task 3: Generic Attribute

In local class LCL_CARRIER, define a private instance attribute flights in which you can store references to instances of LCL_PASSENGER_FLIGHT and LCL_CARGO_FLIGHT at the same time. Fill the attribute in the implementation of the CONSTRUCTOR method with the combined content of attributes passenger_flights and cargo_flights.

Steps

  1. In local class LCL_FLIGHT, use a quick fix to generate a public table type that is based on this class (suggested name for the table type: tab).

    1. Go to the definition of local class LCL_FLIGHT.

    2. In the CLASS statement, place the cursor on lcl_flight and press Ctrl + 1 to invoke the quick fix.

    3. From the list of available quick fixes, choose Generate table type for lcl_flight.

    4. The new TYPES statement should look like this:

      Code Snippet
      Copy code
      Switch to dark mode
      123
      TYPES tab TYPE STANDARD TABLE OF REF TO lcl_flight WITH DEFAULT KEY.
  2. In the private section of local class LCL_CARRIER, define a new attribute flights. Type it with the table type that you just created in local class LCL_FLIGHT.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234
      DATA passenger_flights TYPE lcl_passenger_flight=>tt_flights. DATA cargo_flights TYPE lcl_cargo_flight=>tt_flights.
  3. Adjust the implementation of the CONSTRUCTOR method. After you called method GET_FLIGHTS_BY_CARRIER of LCL_PASSENGER_PLANE, add all object references stored in passenger_flights to flights.

    1. Go to the implementation of CONSTRUCTOR in local class LCL_CARRIER.

    2. Before the ENDMETHOD statement, add the following code:

      Code snippet
      Expand
    3. If you prefer a table comprehension, add the following code:

      Code snippet
      Expand
  4. Similarly, implement a loop over cargo_flights and add these references to flights, too.

    1. Before the ENDMETHOD statement, add the following code:

      Code snippet
      Expand
    2. If you prefer a table comprehension, add the following code:

      Code snippet
      Expand

Task 4: Generic Access and Down-Cast

Eliminate instance attributes passenger_flights and cargo_flights and replace them with the new attribute flights. Where the code accesses specific components of the subclasses, implement a down-cast.

Steps

  1. In the definition part of local class LCL_CARRIER, remove or comment the definition of attributes passenger_flights and cargo_flights.

    1. Select the two DATA statements and press Ctrl + < to add a comment sign in front of each code row.

  2. In method CONSTRUCTOR, use inline declarations to declare passenger_flights and cargo_flights as local variables.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345678
      passenger_flights = lcl_passenger_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ). cargo_flights = lcl_cargo_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ).
  3. After the method calls with the inline declarations, fill a private instance attribute with the number of rows in local variable passenger_flights (suggested name: pf_count).

    Hint

    First implement the value assignment. Then use a quick fix to create the attribute declaration.
    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345
      DATA(passenger_flights) = lcl_passenger_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ).
    2. Place the cursor on pf_count, press Ctrl + 1 and choose Declare attribute pf_count.

  4. Similarly, fill a private instance attribute with the number of rows in local variable cargo_flights (suggested name: cf_count).

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345
      DATA(cargo_flights) = lcl_cargo_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ).
    2. Place the cursor on cf_count, press Ctrl + 1 and choose Declare attribute cf_count.

  5. In method GET_OUTPUT, replace the embedded LINES functions with the attributes you just defined.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345678910
      METHOD get_output. APPEND |{ 'Carrier Name:'(001) } { me->name } | TO r_result. APPEND |{ 'Passenger Flights:'(002) } { } | TO r_result. APPEND |{ 'Average free seats:'(003) } { get_average_free_seats( ) } | TO r_result. APPEND |{ 'Cargo Flights:'(004) } { } | TO r_result. ENDMETHOD.
  6. In method FIND_CARGO_FLIGHT, replace attribute cargo_flights with attribute flights.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12
      * LOOP AT me->cargo_flights INTO DATA(flight)
  7. In the call of method GET_FREE_CAPACITY add a CAST expression.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567
      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 flight ->get_free_capacity( ) >= i_cargo.
  8. To make sure you only consider instances of LCL_CARGO_FLIGHT, extend the WHERE condition of the LOOP.

    Hint

    Remember that in internal tables with elementary row type you address the content with pseudo-component table_line.
    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234
      LOOP AT me->flights INTO DATA(flight) WHERE table_line->flight_date >= i_from_date.
  9. Adjust the implementation of method FIND_PASSENGER_FLIGHT in the same way.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567891011121314151617181920
      LOOP AT INTO DATA(flight) WHERE table_line->flight_date >= i_from_date. 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 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. Adjust the implementation of the GET_AVERAGE_FEE_SEATS method. Add a CAST expression and a WHERE condition to the table reduction.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567891011121314151617181920
      METHOD get_average_free_seats. * Table Reductions ********************************************************************** * 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 NEXT i += <flight> ->get_free_seats( ) ) / . ENDMETHOD.
  11. Activate and test your global class as console app.

    1. Press Ctrl + F3.

    2. Press F9.

Log in to track your progress & complete quizzes